成成你好呀笔记整理(知识点合集七)
作者:互联网
第六十九讲 查找算法
1.静态查找和动态查找
静态查找:
数据集合稳定,不需要添加,删除元素的查找操作。
动态查找:
数据集合在查找的过程中需要同时添加或删除元素的查找操作。
2.查找结构
对于静态查找来说:
我们不妨可以用线性表结构组织数据,这样可以使用顺序查找算法。
如果我们再对关键字进行排序,则可以使用折半查找算法或斐波那契查找算法等来提高查找的效率。
对于动态查找来说:
我们则可以考虑使用二叉排序树的查找技术,另外我们还可以使用散列表结构来解决一些查找问题。
这些技术我们都将在这部分教程里边介绍给大家。
3.顺序查找
顺序查找又叫线性查找,是最基本的查找技术,它的查找过程是:
从第一个(或者最后一个)记录开始,逐个进行记录的关键字和给定值进行比较,若某个记录的关键字和给定值相等,则查找成功。
如果查找了所有的记录仍然找不到与给定值相等的关键字,则查找不成功。
顺序查找算法代码:
// 顺序查找优化方案,a为要查找的数组,n为要查找的数组的长度,key为要查找的关键字
int Sq_Search(int *a, int n, int key)
{
int i = n;
a[0] = key
while( a[i] != key )
{
i--;
}
return i;
}
优化代码:
// 顺序查找,a为要查找的数组,n为要查找的数组的长度,key为要查找的关键字
int Sq_Search(int *a, int n, int key)
{
int i;
for( i=1; i <= n; i++ )
{
if( a[i] == key )
{
return i;
}
}
return 0;
}
课后作业
假设以下有一个结构体存放的是学生的记录,每条记录包括:
学号、姓名、成绩。
请编写一个程序,要求输出0002编号,康小泡大姐大的具体信息。
代码:
#include <stdio.h>
typedef struct student
{
int id;
char name[10];
float score;
}Student;
float search(Student stu[], int n, int key)
{
int i;
for( i=0; i < n; i++ )
{
if( stu[i].id == key )
{
return i.score;
}
}
return -1;
}
int main()
{
Student stu[4] = {
{0000, "小甲鱼", 100},
{0001, "风介", 99},
{0002, "康小泡", 101},
{0003, "不二如是", 33}
};
float score;
score = search(stu, 4, 0002);
printf("0002号康小泡大姐大的成绩是:%f", score);
return 0;
}
第七十讲 插值查找|【按比例查找】
1.插值查找(按比例查找)
如果鱼油忘了,“折半查找”,那么请先去复习:
折半查找又称为二分查找,是一种效率较高的查找算法。
插值查找是二分查找演化而来。
相比于二分查找(折半),该算法考虑的是每次折的时候折多少,即不一定是1/2;
如在一本字典中找"fishc"这个单词。
我们自己来操作肯定是先翻到字典开始的那一小部分,而不是从字典的中间开始进行折半查找。
在二分查找中mid=(low+high)/2=low+1/2*(high-low)。
插值查找就是对1/2,其实就是计算线性比例。
(系数,或者说比例)进行改变,它将1/2变成 (key - array[low])/(array[high] - array[low])
因为插值查找是依赖线性比例的,如果当前数组分布不是均匀的,那么该算法就不合适。
代码实现:
#include <stdio.h>
int bin_search( int str[], int n, int key )
{
int low, high, mid;
low = 0;
high = n-1;
while( low <= high )
{
mid = low + (key-a[low]/a[high]-a[low])*(high-low); // 插值查找的唯一不同点
if( str[mid] == key )
{
return mid;
}
if( str[mid] < key )
{
low = mid + 1;
}
if( str[mid] > key )
{
high = mid - 1;
}
}
return -1;
}
int main()
{
int str[11] = {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89};
int n, addr;
printf("请输入待查找的关键字: ");
scanf("%d", &n);
addr = bin_search(str, 11, n);
if( -1 != addr )
{
printf("查找成功,可喜可贺,可口可乐! 关键字 %d 所在的位置是: %d\n", n, addr);
}
else
{
printf("查找失败!\n");
}
return 0;
}
第七十一讲 斐波那契查找|【黄金分割法查找】
1.黄金比例
2.斐波那契查找
斐波那契数列(前两项之和等于第三项,从1,1开始。F[k]):
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89……
前后两个数字的比值随着数列的增加,越来越接近黄金比值0.618。
比如这里的89,把它想象成整个有序表的元素个数,而89是由前面的两个斐波那契数34和55相加之后的和。
也就是说:
把元素个数为89的有序表分成由前55个数据元素组成的前半段和由后34个数据元素组成的后半段。
那么前半段元素个数和整个有序表长度的比值就接近黄金比值0.618。
假如要查找的元素在前半段,那么继续按照斐波那契数列来看:
55 = 34 + 21
所以继续把前半段分成前34个数据元素的前半段和后21个元素的后半段。
继续查找,如此反复,直到查找成功或失败,这样就把斐波那契数列应用到查找算法中了。
如下图:
从图中可以看出,当有序表的元素个数不是斐波那契数列中的某个数字时,需要把有序表的元素个数长度补齐。
让它成为斐波那契数列中的一个数值,当然把原有序表截断肯定是不可能的,不然还怎么查找。
然后图中标识每次取斐波那契数列中的某个值时(F[k]),都会进行-1操作。
这是因为有序表数组位序从0开始的,纯粹是为了迎合位序从0开始。
算法实现:
#include <stdio.h>
#define MAXSIZE 20
void fibonacci(int *f)
{
int i;
f[0] = 1;
f[1] = 1;
for(i=2; i < MAXSIZE; ++i)
{
f[i] = f[i-2] + f[i-1];
}
}
int fibonacci_search(int *a,int key,int n)
{
int low = 0;
int high = n - 1;
int mid = 0;
int k = 0;
int F[MAXSIZE];
int i;
fibonacci(F);
while( n > F[k]-1 )
{
++k;
}
for( i=n; i < F[k]-1; ++i)
{
a[i] = a[high];
}
while( low <= high )
{
mid = low + F[k-1] - 1;
if( a[mid] > key )
{
high = mid - 1;
k = k - 1;
}
else if( a[mid] < key )
{
low = mid + 1;
k = k - 2;
}
else
{
if( mid <= high )
{
return mid;
}
else
{
return high;
}
}
}
return -1;
}
int main()
{
int a[MAXSIZE] = {1, 5, 15, 22, 25, 31, 39, 42, 47, 49, 59, 68, 88};
int key;
int pos;
printf("请输入要查找的数字:");
scanf("%d", &key);
pos = fibonacci_search(a, key, 13);
if( pos != -1 )
{
printf("\n查找成功,可喜可贺,可口可乐! 关键字 %d 所在的位置是: %d\n\n", key, pos);
}
else
{
printf("\nO~No~~小的办事不力,未在数组中找到元素:%d\n\n", key);
}
return 0;
}
第七十二讲 线性索引查找 |【稠密、分块、倒排】
1.稠密索引
如果记录是排好序的,我们就可以在记录上建立稠密索引。它是这样一系列存储块:
块中只存放记录的键以及指向记录本身的指针,指针就是一个指向记录或存储块地址。
稠密索引文件中的索引块保持键的顺序与文件中的排序顺序一致。
既然我们假定查找键和指针所占存储空间远小于记录本身,我们就可以认为存储索引文件比存储数据文件所需存储块要少得多。
当内存容纳不下数据文件,但能容纳下索引文件时,索引的优势尤为明显。
这时,通过使用索引文件,我们每次查询只用一次I/O操作就能找到给定键值的记录。
2.分块索引
分块查找:
分块查找又称索引顺序查找,它是顺序查找的一种改进方法。
方法描述:
将n个数据元素“按块有序”划分为m块(m<=n)。每一块中的数据元素不必有序,但块与块之间必须“按块有序”,即第1快中的任一元素的关键字都必须小于第2块中任一元素的关键字;
而第2块中任一元素又都小于第3块中的任一元素,……
图示分块如下:
操作步骤:
1、先选取各快中的最大关键字构成一个索引表
2、查找分两部分:
先对索引表进行二分查找或顺序查找,以确定待查记录在哪一块中;
然后在已确定的快中用顺序法进行查找。
分块查找平均查找长度:
设长度为n的表均匀地分成b块,每块含有s个记录,则:
b=n/s
顺序查找所在块,分块查找的平均查找长度为:
(b+1)/2 + (s+1)/2 = (n/s+s)/2+1
折半查找所在块,分块查找的平均查找长度为:
log2(n/s+1)+s/2
3.倒排索引
倒排索引是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。
它是文档检索系统中最常用的数据结构。
有两种不同的反向索引形式:
一条记录的水平反向索引(或者反向档案索引)包含每个引用单词的文档的列表。
一个单词的水平反向索引(或者完全反向索引)又包含每个单词在一个文档中的位置。
后者的形式提供了更多的兼容性(比如短语搜索),但是需要更多的时间和空间来创建。
现在有两个数组:A和B
A:
I love FishC.com
B:
I am learning in FishC.com
第七十三讲 二叉排序树 | 【了解】
1.二叉排序树
二叉排序树又称“二叉查找树”、“二叉搜索树”。
二叉排序树具有下列性质:
1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3. 它的左、右子树也分别为二叉排序树。
二叉排序树通常采用二叉链表作为存储结构。
中序遍历二叉排序树可得到一个依据关键字的有序序列,一个无序序列可以通过构造一棵二叉排序树变成一个有序序列。
构造树的过程即是对无序序列进行排序的过程。
每次插入的新的结点都是二叉排序树上新的叶子结点。
在进行插入操作时,不必移动其它结点,只需改动某个结点的指针,由空变为非空即可。
第七十四讲 二叉排序树|【查找、插入】
上面就是一个二叉排序树,具有如下性质:
1、就是若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
2、若它的右子树不空,则右子树上所有节点的值均大于其根节点的值。
3、任何节点的键值一定大于其左子树中的每一个节点的键值,并小于其右子树中的每一个节点的键值。
1.二叉排序树查找
要在二叉树中找出查找最大最小元素是极简单的事情。
从根节点一直往左走,直到无路可走就可得到最小值;
从根节点一直往右走,直到无路可走,就可以得到最大值。
代码:
// 二叉树的二叉链表结点结构定义
typedef struct BiTNode
{
int data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
// 递归查找二叉排序树 T 中是否存在 key
// 指针 f 指向 T 的双亲,其初始值调用值为 NULL
// 若查找成功,则指针 p 指向该数据元素结点,并返回 TRUE
// 否则指针 p 指向查找路径上访问的最后一个结点,并返回 FALSE
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p)
{
if( !T ) // 查找不成功
{
*p = f;
return FALSE;
}
else if( key == T->data ) // 查找成功
{
*p = T;
return TRUE;
}
else if( key < T->data )
{
return SearchBST( T->lchild, key, T, p ); // 在左子树继续查找
}
else
{
return SearchBST( T->rchild, key, T, p ); // 在右子树继续查找
}
}
2.二叉排序树插入
插入新元素时,可以从根节点开始,遇键值较大者就向左。
遇键值较小者就向右,一直到末端,就是插入点。
代码:
// 当二叉排序树 T 中不存在关键字等于 key 的数据元素时,
// 插入 key 并返回 TRUE,否则返回 FALSE
Status InsertBST(BiTree *T, int key)
{
BiTree p, s;
if( !SearchBST(*T, key, NULL, &p) )
{
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
if( !p )
{
*T = s; // 插入 s 为新的根结点
}
else if( key < p->data )
{
p->lchild = s; // 插入 s 为左孩子
}
else
{
p->rchild = s; // 插入 s 为右孩子
}
return TRUE;
}
else
{
return FALSE; // 树中已有关键字相同的结点,不再插入
}
}
第七十五讲 二叉排序树 | 【删除】
1.二叉排序树的删除
对于一般的二叉树来说,删去树中的一个结点是没有意义的,因为:
它将使以被删除的结点为根的子树变成森林,破坏了整棵树的结构
但是,对于二叉排序树,删去树上的一个结点相当于删去有序序列中的一个记录。
只要在删除某个结点后不改变二叉排序树的特性即可。
在二叉排序树上删除一个结点的算法如下:
btree * DeleteBST(btree *b, ElemType x)
{
if (b)
{
if (b->data == x)
b = DelNode(b);
else if (b->data > x)
b->lchild = DeleteBST(b->lchild, x);
else
b->rchild = DeleteBST(b->rchild, x);
}
return b;
}
2.删除过程
删除过程有两种方法。
第一种过程如下:
1、若p有左子树,找到其左子树的最右边的叶子结点r,用该叶子结点r来替代p,把r的左孩子作为r的父亲的右孩子。
2、若p没有左子树,直接用p的右孩子取代它。
代码:
btree * DelNode(btree *p)
{
if (p->lchild)
{
btree *r = p->lchild; //r指向其左子树;
while(r->rchild != NULL)//搜索左子树的最右边的叶子结点r
{
r = r->rchild;
}
r->rchild = p->rchild;
btree *q = p->lchild; //q指向其左子树;
free(p);
return q;
}
else
{
btree *q = p->rchild; //q指向其右子树;
free(p);
return q;
}
}
第二种过程如下:
1、若p有左子树,用p的左孩子取代它;找到其左子树的最右边的叶子结点r,把p的右子树作为r的右子树。
2、若p没有左子树,直接用p的右孩子取代它。
代码:
btree * DelNode(btree *p)
{
if (p->lchild)
{
btree *r = p->lchild; //r指向其左子树;
btree *prer = p->lchild; //prer指向其左子树;
while(r->rchild != NULL)//搜索左子树的最右边的叶子结点r
{
prer = r;
r = r->rchild;
}
if(prer != r)//若r不是p的左孩子,把r的左孩子作为r的父亲的右孩子
{
prer->rchild = r->lchild;
r->lchild = p->lchild; //被删结点p的左子树作为r的左子树
}
r->rchild = p->rchild; //被删结点p的右子树作为r的右子树
free(p);
return r;
}
else
{
btree *q = p->rchild; //q指向其右子树;
free(p);
return q;
}
}
点评:
两种方法各有优劣,第一种操作简单一点点,但均衡性不如第二种,因为它将结点p的右子树全部移到左边来了。
但是第一种方法,把r移来移去,很容易出错,其实在这里我们删除的只是p的元素值,而不是它的地址,所以完全没有必要移动指针。
3.算法实现
仔细观察:
发现我们删除的地址实际上是p的左子树的最右边的叶子结点r的地址。
所以我们只要把r的数据填到p中,然后把r删除即可。
算法如下:
btree * DelNode(btree *p)
{
if (p->lchild)
{
btree *r = p->lchild; //r指向其左子树;
btree *prer = p->lchild; //prer指向其左子树;
while(r->rchild != NULL)//搜索左子树的最右边的叶子结点r
{
prer = r;
r = r->rchild;
}
p->data = r->data;
if(prer != r)//若r不是p的左孩子,把r的左孩子作为r的父亲的右孩子
prer->rchild = r->lchild;
else
p->lchild = r->lchild; //否则结点p的左子树指向r的左子树
free(r);
return p;
}
else
{
btree *q = p->rchild; //q指向其右子树;
free(p);
return q;
}
}
第七十六讲 平衡二叉树 |【AVL】
1.平衡二叉排序树
二叉查找树不是严格的O(logN),导致了在真实场景中没有用武之地,谁也不愿意有O(N)的情况发生,
当有很多数据灌到我的树中时,肯定会希望最好是以“完全二叉树”的形式展现。
这样我才能做到“查找”是严格的O(logN),比如把这种”树“调正到如下结构:
这里就涉及到了“树节点”的旋转,涉及到本讲重点。
定义:
父节点的左子树和右子树的高度之差不能大于1,也就是说不能高过1层,否则该树就失衡了,此时就要旋转节点,在
编码时,我们可以记录当前节点的高度,比如空节点是-1,叶子节点是0,非叶子节点的height往根节点递增
比如在下图中我们认为树的高度为h=2:
四种旋转情况
① 左左情况(左子树的左边节点):
我们看到,在向树中追加“节点1”的时候,根据定义我们知道这样会导致了“节点3"失衡。
满足“左左情况“,可以这样想,把这棵树比作齿轮,我们在“节点5”处把齿轮往下拉一个位置。
也就变成了后面这样“平衡”的形式,如果用动画解释就最好理解了。
② 右右情况(右子树的右边节点):
同样,”节点5“满足”右右情况“,其实我们也看到,这两种情况是一种镜像。
当然操作方式也大同小异,我们在”节点1“的地方将树往下拉一位,最后也就形成了我们希望的平衡效果。
③左右情况(左子树的右边节点):
从图中我们可以看到,当我们插入”节点3“时,“节点5”处失衡。
注意,找到”失衡点“是非常重要的。
当面对”左右情况“时,我们将失衡点的左子树进行"右右情况旋转"。
然后进行”左左情况旋转“,经过这样两次的旋转就OK了。
④右左情况(右子树的左边节点):
这种情况和“情景3”也是一种镜像关系,很简单。
我们找到了”节点15“是失衡点,然后我们将”节点15“的右子树进行”左左情况旋转“,
然后进行”右右情况旋转“,最终得到了我们满意的平衡。
第七十七讲 平衡二叉树的实现原理
1.平衡二叉树的实现原理
核心:
平衡二叉树构建的基本思想就是在构建二叉排序树的过程中,每当插入一个结点。
就立刻先检查是否因插入这个结点而导致树的平衡性遭到破坏,如果是,立刻找出最小不平衡子树。
然后通过一定的规律进行调整,使之成为新的平衡子树。
调整是什么呢?
就是:
旋转
2.实例
选取以下10个数据构造平衡二叉树:
2,1,0,3,4,5,6,9,8,7
下面的内容中:
L代表左节点,R代表右节点。
1、首先数据为2的结点作为根结点插入,接着插入1,仍是平衡的。
再插入0是,2的平衡因子变为2,此时出现了不平衡,因此需要进行调整。
最低不平衡结点为2,属于LL型,根据上述网址的内容,则调整过程如下:
2、接着插入3,是平衡的,再插入4,此时出现了不平衡。
结点 1 和 2 的平衡因子都为 -2,结点2为最低不平衡结点,属于RR型,调整过程如下所示:
3、接着插入5,此时结点 1 的平衡因子为 -2,导致不平衡。
结点1为最低不平衡结点,属于RR型,调整如下:
4、接着插入6,此时结点4的平衡因子为 -2,导致不平衡。
结点4为最低不平衡结点,属于RR型,调整如下:
5、接着插入9,是平衡的,再插入8,此时结点 3、5、6 的平衡因子都为 -2,导致不平衡。
结点6为最低不平衡结点,属于RL型,调整如下:
6、插入7,此时结点3、5的平衡因子为 -2,导致不平衡。
最低不平衡结点为5,属于RL型,调整如下:
第七十八讲 平衡二叉树的实现原理|【代码实现】
1.代码实现:
#define LH 1
#define EH 0
#define RH -1
typedef struct BiTNode
{
int data;
int bf;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
void R_Rotate(BiTree *p)
{
BiTree L;
L = (*p)->lchild;
(*p)->lchild = L->rchild;
L->rchild = (*p);
*p = L;
}
void LeftBalance(BiTree *T)
{
BiTree L, Lr;
L = (*T)->lchild;
switch(L->bf)
{
case LH:
(*T)->bf = L->bf = EH;
R_Rotate(T);
break;
case RH:
Lr = L->rchild;
switch(Lr->bf)
{
case LH:
(*T)->bf = RH;
L->bf = EH;
break
case EH:
(*T)->bf = L->bf = EH;
break;
case RH:
(*T)->bf = EH;
L->bf = LH;
break;
}
Lr->bf = EH;
L_Rotate(&(*T)->lchild);
R_Rotate(T);
}
}
int InsertAVL(BiTree *T, int e, int *taller)
{
if( !*T )
{
*T = (BiTree)malloc(sizeof(BiTNode));
(*T)->data = e;
(*T)->lchild = (*T)->rchild = NULL;
(*T)->bf = EH;
*taller = TRUE;
}
else
{
if(e == (*T)->data)
{
*taller = FALSE;
return FALSE;
}
if(e < (*T)->data)
{
if(!InsertAVL(&(*T)->lchild, e, taller))
{
return FALSE;
}
if(*taller)
{
switch((*T)->bf)
{
case LH:
LeftBalance(T);
*taller = FALSE;
break;
case EH:
(*T)->bf = LH;
*taller = TRUE;
break;
case RH:
(*T)->bf = EH;
*taller = FALSE;
break;
}
}
}
else
{
if(!InsertAVL(&(*T)->rchild, e, taller))
{
return FALSE;
}
if(*taller)
{
switch((*T)->bf)
{
case LH:
(*T)->bf = EH;
*taller = FALSE;
break;
case EH:
(*T)->bf = RH;
*taller = TRUE;
break;
case RH:
RightBalance(T);
*taller = FALSE;
break;
}
}
}
}
}
标签:左子,知识点,成成,int,结点,二叉,查找,合集,lchild 来源: https://blog.csdn.net/weixin_43144288/article/details/116044906