数据结构期末复习(第九章 查找)
文章目录
Part 1、知识点总结
- 查找表(Search Table):相同类型的数据元素(对象)组成的集合,每个元素通常由若干数据项构成。
- 关键字(Key,码):数据元素中某个(或几个)数据项的值,它可以标识一个数据元素。若关键字能唯一标识一个数据元素,则关键字称为主关键字;将能标识若干个数据元素的关键字称为次关键字。
- 查找/检索(Searching):根据给定的K值,在查找表中确定一个关键字等于给定值的记录或数据元素。
- 查找表中存在满足条件的记录:查找成功;
- 结果:所查到的记录信息或记录在查找表中的位置。
- 查找表中不存在满足条件的记录:查找失败。
- 查找方法评价指标
查找过程中主要操作是关键字的比较,查找过程中关键字的平均比较次数(平均查找长度ASL:Average Search Length)作为衡量一个查找算法效率高低的标准。
查找有两种基本形式:静态查找和动态查找。
- 静态查找(Static Search):在查找时只对数据元素进行查询或检索,查找表称为静态查找表。
- 动态查找(Dynamic Search):在实施查找的同时,插入查找表中不存在的记录,或从查找表中删除已存在的某个记录,查找表称为动态查找表。
根据存储结构的不同,查找方法可分为三大类:
- ①线性表查找法
- ②树表示查找法
- ③哈希(计算式)查找法
1.1 静态查找表
顺序查找
-
查找思想:
从表的一端开始逐个将记录的关键字和给定K值进行比较,若某个记录的关键字和给定K值相等,查找成功;否则,若扫描完整个表,仍然没有找到相应的记录,则查找失败。
-
算法分析:
设查找每个记录成功的概率相等,即Pi=1/n;查找第i个元素成功的比较次数Ci=n-i+1 ;
查找成功时的平均查找长度ASL:
包含查找不成功时:查找失败的比较次数为n+1,若成功与不成功的概率相等,对每个记录的查找概率为Pi=1/(2n),则平均查找长度ASL:
1.2 折半查找
- 查找思想(升序存储)
用Low、High和Mid表示待查找区间的下界、上界和中间位置指针,初值为Low=1,High=n
⑴ 取中间位置Mid:Mid= (Low+High)/2
⑵ 比较中间位置记录的关键字与给定的K值:
① 相等: 查找成功;
② 大于:待查记录在区间的前半段,修改上界指针: High=Mid-1,转⑴ ;
③ 小于:待查记录在区间的后半段,修改下界指针:Low=Mid+1,转⑴ ;
直到越界(Low>High),查找失败。
struct SSTable{
KeyType elem[MAX];
int length;
}
int Bin_Search(SSTable ST , KeyType key)
{ int Low=1,High=ST.length, Mid ;
while (Low<High)
{ Mid=(Low+High)/2 ;
if (ST. elem[Mid]== key)
return(Mid) ;
else if (ST. elem[Mid]< key)
Low=Mid+1 ;
else High=Mid-1 ;
}
return(0) ; /* 查找失败 */
}
- 算法分析
折半查找过程可用判定树(二叉树)表示;
从根到被查结点路径关键字比较次数为被查结点层数;
如果查找成功,那么比较次数最多不超过树的深度 |㏒2n|+1
1.3 分块查找
分块查找(Blocking Search)又称索引顺序查找,是前面两种查找方法的综合。
-
查找表的组织
① 将查找表分成几块。块间有序,即第i+1块的所有记录关键字均大于(或小于)第i块记录关键字;块内无序。
② 在查找表的基础上附加一个索引表,索引表是按关键字有序的,索引表中记录的构成是: -
查找思想
先确定待查记录所在块(顺序或折半),再在块内查找(顺序查找)。 -
算法实例
-
算法分析
设表长为n个记录,均分为b块,每块记录数为s,则b=⌈n/s⌉。设记录的查找概率相等,每块的查找概率为1/b,块中记录的查找概率为1/s,则平均查找长度ASL:
1.4 动态查找
动态查找(Dynamic Search):在实施查找的同时,插入查找表中不存在的记录,或从查找表中删除已存在的某个记录,查找表称为动态查找表。
1.4.1 动态查找-BST树
二叉排序树(BST)是空树或者是满足下列性质的二叉树:
- 若左子树不为空,则左子树上所有结点的值(关键字)都小于根结点的值;
- 若右子树不为空,则右子树上所有结点的值(关键字)都大于根结点的值;
- 左、右子树都分别是二叉排序树。
1.4.2 BST树的插入
-
插入思想
在BST树中插入一个新结点x时,若BST树为空,则令新结点x为插入后BST树的根结点;否则,将结点x的关键字与根结点T的关键字进行比较:
① 若相等: 不需要插入;
② 若x.keykey:结点x插入到T的左子树中
③ 若x.key>T->key:结点x插入到T的右子树中 -
算法实现
void Insert_BST (BSTNode *T , KeyType key){
BSTNode *x ;
x=(BSTNode *)malloc(sizeof(BSTNode)) ;
x->key=key; x->Lchild=x->Rchild=NULL ;
if (T==NULL) T=x ;
else{
if (T->key==x->key) return ;/*已有结点 */
else if (x->key< T->key )
Insert_BST(T->Lchild, key) ;
else Insert_BST(T->Rchild, key) ; }
}
结论:输入序列值相同,序列顺序不同,构造的二叉树形态不同
1.4.3 BST树的查找
- 查找思想
首先将给定的K值与二叉排序树的根结点的关键字进行比较:若相等: 则查找成功;
① 给定的K值小于BST的根结点的关键字:继续在该结点的左子树上进行查找;
② 给定的K值大于BST的根结点的关键字:继续在该结点的右子树上进行查找。 - 算法实现
BSTNode* BST_Search(BSTNode *T , KeyType key)
{ if (T==NULL) return(NULL) ;
else {
if (T->key==key)) return(T) ;
else if ( key<T->key )
return(BST_Serach(T->Lchild, key)) ;
else return(BST_Serach(T->Rchild, key)) ;
}
}
- 性能分析
最好情况:形态类似二分查找的判定树,时间复杂度近似log2n
最坏情况:单支树,查找类似链表顺序查找,平均复杂度(n+1)/2
由此可见,二叉树的形态不同,查找性能不同
1.5 平衡二叉树(AVL)
平衡二叉树是空树,或者是满足下列性质的二叉树:
- ⑴左子树和右子树深度之差的绝对值不大于1;
- ⑵左子树和右子树也都是平衡二叉树。
平衡因子(Balance Factor) :二叉树上结点的左子树的深度减去其右子树深度称为该结点的平衡因子。
如果一棵二叉树既是二叉排序树又是平衡二叉树,称为平衡二叉排序树。
如果在一棵AVL树中插入一个新结点,就有可能造成失衡,此时必须重新调整树的结构,使之恢复平衡。我们称调整平衡过程为平衡旋转。
1.6 哈希(散列)查找
- 比较式查找通过对关键字的一系列查找比较实现,效率与查找的表长有关。
- 思考:当关键字可能取值的集合远远大于实际表长时,一一对应的比较查找法效率低。
- 计算式查找对关键字进行计算得到对应元素相关的地址H(key)=Addr。
- 计算式查找法:哈希法(又称散列法)
基本思想:
(1)哈希函数:p=H(key),计算关键字key在哈希表中的存储位置p;
(2)冲突问题:若关键字key1和key2,哈希函数值H(key1)=H(key2),则发生冲突。
同义词:具有相同函数值的两个不同的关键字,称为该哈希函数的同义词。
哈希法既是一个建表方法,又是一个查表方法 - 冲突原因:
(1)必然性: 关键字可能取值空间远远大于哈希表的地址空间,冲突不可避免;
(2)可能性:哈希函数H(key)的散列性能不好,也可能会加剧冲突。 - 哈希函数是一种映象,其设定很灵活,只要使任何关键字的哈希函数值都落在表长允许的范围之内即可。
- 哈希函数“好坏”的主要评价因素有:
(1)函数本身便于计算;
(2)计算出来的地址分布均匀。
1.6.1 哈希函数的构造
- 直接定址法
取关键字或关键字的某个线性函数作哈希地址,即H(key)=key或H(key)=a·key+b(a,b为常数)
特点:直接定址法所得地址集合与关键字集合大小相等,不会发生冲突,但实际中很少使用 - 数字分析法
取关键字中分布比较均匀的若干位作为哈希地址。
适用于关键字位数比哈希地址位数大,且可能出现的关键字事先知道的情况。
- 平方取中法
将关键字平方后取中间几位作为哈希地址。
一个数平方后中间几位和数的每一位都有关,故不同关键字会以较高概率产生不同的哈希地址。
这种方法适于不知道全部关键字情况,是一种较为常用的方法。 - 将关键字分割成位数相同的几部分(最后一部分可以不同),然后取这几部分的叠加和作为哈希地址。
数位叠加有移位叠加和间界叠加两种。- 移位叠加:将分割后的几部分低位对齐相加
- 间界叠加:从一端到另一端沿分割界来回折迭,然后对齐相加
- 除留余数法
哈希函数为H(key)=key%p (p≤m),表长m,p的选择很关键:
p选为基数的倍数,相当于尾部截断,冲突多;K=1998(10),P=102(10的倍数100),H(k)=98
p应为小于等于m的最大素数
- 随机数法
取关键字的随机函数值作哈希地址,即H(key)=random(key)
1.6.2 冲突处理的方法
- 开放定址法
基本方法:当p=H(key)发生冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p 为基础,产生另一个哈希地址p2,…,直到找到一个空地址(开放的地址)pi为止,将发生相应记录放到该地址中。
通用的再散列函数形式是:
Hi(key)=(H(key)+di)%m,i=1, 2, …, k(k≤m-1)- 线性探测法
Hi=(H(key)+di)%m,di=1,2,3,…,m-1
特点:当发生冲突时,从初次发生冲突的位置依次向后探测其他的地址,直到找到一个空单元或查遍全表。 - 二次探测法
增量序列为:di=1²,-1²,2²,-2²,3²,…,±k² (k≤⌊m/2⌋)
特点:当发生冲突时,在表的左右进行跳跃式探测。 - 伪随机探测法
di=伪随机序列。
- 线性探测法
- 再哈希法
构造若干个哈希函数,当发生冲突时,利用不同的哈希函数再计算下一个新哈希地址,直到不发生冲突为止。即:
Hi=RHi(key) i=1, 2, …, k
优点:不易产生冲突的“聚集”现象;
缺点:计算时间增加。 - 链地址法
将所有关键字为同义词(散列地址相同)的记录存储在一个单链表中,并用一维数组存放链表的头指针。
- 建立公共溢出区
在基本散列表之外,另外设立一个溢出表保存与基本表中记录冲突的所有记录。
1.6.3 哈希构造
- 哈希方法:包括函数构造和处理冲突两方面
- 哈希函数构造规则:计算简单,分布均匀;
常用方法:除留余数法H(key)=key%p (p≤m) - 处理冲突的主要方法:
开放地址法(线性探测、二次探测)
链地址法
1.6.4 哈希查找过程及分析
- 哈希查找过程
- 查找算法
int Hash_search(RecType HT[], KeyType k, int m)
/* 查找散列表HT中的关键字K,用开放定址法解决冲突 */
{ int h, j ;
h=h(k) ;
while (j<m && !EQ(HT[h].key, NULLKEY) )
{ if (EQ(HT[h].key, k) ) return(h) ;
else h=R(k, ++j) ;
}
return(-1) ;
}
HNode * hash_search(HNode *t[], KeyType k)
{ HNode *p; int i;
i=h(k);
if (t[i]==NULL) return(NULL);
p=t[i];
while(p!=NULL){
if (EQ(p->key, k)) return(p);
else p=p->link;}
return(NULL);
} /* 查找散列表HT中的关键字K,用链地址法解决冲突 */
- 哈希查找分析(ASL)
哈希查找时关键字与给定值比较的次数取决于:
(1)哈希函数;
(2)处理冲突的方法;
(3)哈希表的填满因子α。
哈希查找的平均查找长度ASL计算方式:
各种散列函数所构造的散列表的ASL如下:
PPT课后题
⑴ 对于一个有n个元素的线性表,若采用顺序查找方法时的平均查找长度是什么?若结点是有序的,则采用折半查找法是的平均查找长度是什么?
⑵ 设查找表采用单链表存储,请分别写出对该表进行顺序查找的静态查找和动态查找的算法。
⑶ 设二叉排序树中的关键字互不相同:则
① 最小元素无左孩子,最大元素无右孩子,此命题是否正确?
② 最大和最小元素一定是叶子结点吗?
③ 一个新结点总是插入在叶子结点上吗?
⑷ 试比较哈希表构造时几种冲突处理方法的优点和缺点。
⑸ 将关键字序列(10, 2, 26, 4, 18, 24, 21, 15, 8, 23, 5, 12, 14)依次插入到初态为空的二叉排序树中,请画出所得到的树T; 然后画出删除10之后的二叉排序树T1 ; 若再将10插入到T1中得到的二叉排序树T2是否与T1相同? 请给出T2的先序、中序和后序序列。
⑹ 设有关键字序列为:(Dec, Feb, Nov, Oct, June, Sept, Aug, Apr, May, July, Jan, Mar) ,请手工构造一棵二叉排序树。该树是平衡二叉排序树? 若不是,请为其构造一棵平衡二叉排序树。
⑺ 设关键字序列是(19, 14, 23, 01, 68, 84, 27, 55, 11, 34, 79),散列表长度是11,散列函数是H(key)=key MOD 11,
① 采用开放地址法的线性探测方法解决冲突,请构造该关键字序列的哈希表。
② 采用开放地址法的二次探测方法解决冲突,请构造该关键字序列的哈希表。
⑻ 试比较线性索引和树形索引的优点和缺点。
⑼ 设关键字序列是(19, 24, 23, 17, 38, 04, 27, 51, 31, 34, 69),散列表长度是11,散列函数是H(key)=key MOD 11,
① 采用开放地址法的线性探测方法解决冲突,请构造该关键字序列的哈希表。
② 求出在等概率情况下,该方法的查找成功和不成功的平均查找长度ASL。
⑽ 下图是一棵3阶B_树,请画出插入关键字B,L,P,Q后的树形。
自己康的一点题
暂无
Part 2、代码
//折半查找
#include <stdio.h>
#include <stdlib.h>
#define ERROR -1
#define max 100
typedef struct{
int *data;
int length;
}arr;
void create(arr *t)
{
t->data = (int*)malloc((max)*sizeof(int));
t->length = 0;
}
void init(arr *t)
{
int tmp;
printf("输入元素,空格分隔,9999退出;\n");
for(int i = 0;;i++)
{
scanf("%d",&tmp);
if(tmp == 9999) break;
t->data[i] = tmp;
t->length ++;
}
}
void print(arr *t)
{
for(int i = 0;i < t->length;i++)
printf("%d ",t->data[i]);
}
int search(int key,arr *t)
{
int low = 0,high = t->length-1,mid;
while(low <= high)
{
mid = (low+high)/2;
if(t->data[mid] == key)
return mid;
else if(t->data[mid] < key)
low = mid+1;
else
high = mid -1;
}
return ERROR;
}
int main()
{
arr a;
create(&a);
init(&a);
// print(&a);
int key;
printf("请输入你要查找的元素:\n");
scanf("%d",&key);
int answer = search(key,&a);
if(answer == ERROR)
printf("查找失败!\n");
else
printf("查找成功!下标位于%d",answer);
return 0;
}
//分块查找
#include <stdio.h>
#include <stdlib.h>
#define ERROR -1
#define max 100
#define end -1
#define ok 1
typedef struct{
int *data;
int length;
}arr;
typedef struct{
int *data;
int length;
int edge;
}block;
void create(arr *t)
{
t->data = (int*)malloc((max)*sizeof(int));
t->length = 0;
}
void init(arr *t)
{
int tmp;
printf("输入元素,空格分隔,9999退出;\n");
for(int i = 0;;i++)
{
scanf("%d",&tmp);
if(tmp == 9999) break;
t->data[i] = tmp;
t->length ++;
}
}
void print(arr *t)
{
for(int i = 0;i < t->length;i++)
printf("%d ",t->data[i]);
}
int devide(arr *t,block *b)
{
block *p;
p = b;
p->length = 0;
int number,edge,count = 1,n[max] = {0};
printf("请输入分块,每块区间长度:\n");
scanf("%d",&edge);
number = t->data[t->length-1]/edge;
if(t->data[t->length-1]>(number*edge)) number++;
printf("共%d个区块\n",number);
for(int i = 0;i < t->length;i++)
{
if(t->data[i] < count*edge)
{
// printf("%d ",t->data[i]);
p->length = p->length+1;
}
else{
// printf("\n%d ",t->data[i]);
p++;
i--;
p->length = 0;
count++;
}
}
p = b;
for(int i = 0;i < number;i++)
{
p->data = (int*)malloc((p->length)*sizeof(int));
p->edge = (i+1)*edge;
p++;
}
p = b;
int j = 0;
count = 1;
for(int i = 0;i < t->length;i++)
{
if(t->data[i] < count*edge)
{
p->data[j] = t->data[i];
j++;
}
else{
i--;
j = 0;
count++;
p++;
}
}
p++;
p->data[0] = end;
// printf("\n%d\n",count);
//number是分的区块个数;
return number;
}
void print2(block *b)
{
int count = 0;
while(b->data[0] != end)
{
for(int i = 0;i < b->length;i++)
printf("%d ",b->data[i]);
printf("\n");
b++;
// count++;
// printf("%d\n",b->length);
// b++;
}
}
int search(int key,block *b,int number)
{
block *p;
int count = 0;
p = b;
int i;
for(i = 0;i < number;i++)
{
if(key < p->edge)
break;
else
p++;
}
count = i;
for(i = 0;i < p->length;i++)
if(key == p->data[i])
break;
printf("第%d块第%d个",count+1,i+1);
return ok;
}
int main()
{
arr a;
block b[max];
int number;
create(&a);
init(&a);
// print(&a);
number = devide(&a,b);
print2(b);
int key;
printf("请输入要查找的值:\n");
scanf("%d",&key);
search(key,b,number);
return 0;
}
Part 3、总结
也算是有始有终了吧。。。
过一段再康题