第七章学习小结
本章学习了三种不同结构的查找表:线性表、树表和散列表。查找的术语包括:查找表、关键字(主关键字、次关键字)、查找(查找成功、查找不成功)、动态查找表和静态查找表、平均查找长度(ASL)
(一)线性表的查找
1、顺序查找
1 typedef struct //数据元素类型定义 2 { 3 KeyType key; 4 InfoType otherinfo; 5 }ElemType; 6 typedef struct //顺序表的定义 7 { 8 ElemType *R; //基地址 9 int length; 10 }SSTable; 11 12 //查找 13 int Search_Seq(SSTable ST, KeyType key) 14 { 15 for(i=ST.length; i>=1; --i) 16 if(ST.R[i].key==key) 17 return i; //从后往前找 18 return 0; 19 } 20 //or设置监视哨的顺序查找 21 int Search_Seq(SSTable ST, KeyType key) 22 { 23 ST.R[0].key=key; //“哨兵” 24 for(i=ST.length; ST.R[i].key!=key; --i) 25 return i; 26 }
时间复杂度为O(n),空间复杂度为O(1)。
优点:对表结构无任何要求,既适用于顺序结构,也适用于链式结构,无论记录是否按关键字有序均可应用。
缺点:平均查找长度较大,查找效率较低,所以当n很大时,不宜采用顺序查找。
习题:
n个数存在一维数组A[1...n]中,在进行顺序查找时,这n个数的排列有序或无序其平均查找长度ASL不同。----正确
析:查找概率相等时,ASL相同;查找概率不等时,如果从前向后查找,则按查找概率由大到小排列的有序表其ASL要比无序表ASL小。
2、折半查找/二分查找
1 //非递归算法 2 int Search_Bin(SSTable ST, KeyType key) 3 { 4 low=1; high=ST.length; 5 while(low<=high) 6 { 7 mid=(low+high)/2; 8 if(key==ST.R[mid].key) return mid; 9 else if(key<ST.R[mid],key) high=mid-1; //前一子表查找 10 else low=mid+1; //后一子表查找 11 } 12 return 0; 13 } 14 //递归算法 15 int Search_Bin(SSTable ST, KeyType key, int low, int high) 16 { 17 if(low>high) 18 return 0; 19 mid=(low+high)/2; 20 if(key==ST.elem[mid].key) 21 return mid; 22 else if(key<ST.elem[mid].key) 23 return Search_Bin(ST, key, low, mid-1) //递归 24 else 25 return Search_Bin(ST, key, mid+1, high) //递归 26 }
时间复杂度为O(log2n)。
优点:比较次数少,查找效率高。
缺点:对表结构要求高,只能用于顺序存储的有序表。查找前需要排序。不适用于数据元素经常变动的线性表。
3、分块查找/索引顺序查找
特点:块间有序,块内无序
查找效率:ASL=Lb+Lw 。Lb :对索引表查找的ASL;Lw :对块内查找的ASL。
查找块:顺序查找 Or 折半查找(更优);块内查找:顺序查找。
优点:由于块内是无序的,故插入和删除比较容易,无需进行大量移动。如果线性表既要快速查找又经常动态变化,则可采用分块查找。
缺点:要增加一个索引表的存储空间并对初始索引表进行排序运算。
(二)树表的查找
1、二叉排序树
空树 Or 具有下列性质的二叉树:
(1)若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3)它的左、右子树也分别为二叉排序树。
二叉排序树是递归定义的。一个重要性质:中序遍历一棵二叉树时可以得到一个结点值递增的有序序列。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 typedef struct //结点的数据域结构 2 { 3 KeyType key; 4 InfoType otherinfo; 5 }ElemType; 6 typedef struct BSTNode //结点结构 7 { 8 ElemType data; 9 struct BSTNode *lchild, *rchild; 10 }BSTNode, *BSTree; 11 12 //递归查找 13 BSTree SearchBST(BSTree T, KeyType key) 14 { 15 if((!T)||key==T->data.key) return T; 16 else if(key<T->data.key) return SearchBST(T->lchild, key); 17 else return SearchBST(T->rchild, key); 18 } 19 20 //插入--插入的元素一定在叶结点上 21 void InsertBST(BSTree &T, ElemType e) 22 { 23 if(!T) 24 {//找到插入位置,递归结束 25 S=new BSTNode; //生成新结点*S 26 S->data=e; //新结点*S的数据域置为e 27 S->lchild=S->rchild=NULL; //新结点*S作为叶子结点 28 T=S; //把新结点*S链接到已找到的插入位置 29 } 30 else if(e.key<T->data.key) 31 InsertBST(T->lchild, e); //将*S插入左子树 32 else if(e.key>T->data.key) 33 InsertBST(T->rchild, e); //将*S插入右子树 34 } 35 36 //创建 37 void CreateBST(BSTree &T) 38 {//依次读入一个关键字为key的结点,将此结点插入二叉排序树T中 39 T=NULL; //将二叉排序树T初始化为空树 40 cin>>e; 41 while(e.key!=ENDFLAG) //ENDFLAG为自定义常量,作为输入结束标志 42 { 43 InsertBST(T, e); //将此结点插入二叉排序树T中 44 cin>>e; 45 } 46 } 47 48 //删除 49 void DeleteBST(BSTree &T, KeyType key) 50 {//从二叉排序树T中删除关键字等于key的结点 51 p=T; f=NULL; //初始化 52 /*------下面的while循环从根开始查找关键字等于key的结点*p------*/ 53 while(p) 54 { 55 if(p->data.key==key) break;//找到关键字等于key的结点*p 56 f=p; //*f为*p的双亲结点 57 if(p->data.key>key) p=p->lchild; //在*p的左子树中继续查找 58 else p=p->rchild; //在*p的右子树中继续查找 59 } 60 if(!p) return; //找不到被删结点则返回 61 /*---考虑3种情况实现p所指子树内部的处理:*p左右子树均不空、无右子树、无左子树-----*/ 62 if((p->lchild)&&(p->rchild)) //被删结点*p左右子树均不空 63 { 64 q=p; s=p->lchild; 65 while(s->rchild) 66 //在*p的左子树中继续查找其前驱家电,即最右下结点 67 { 68 q=s; s=s->rchild; //向右到尽头 69 } 70 p->data=s->data; //s指向被删结点的“前驱” 71 if(q!=p) q->rchild=s->lchild; //重接*q的右子树 72 else q->lchild=s->lchild; //重接*q的左子树 73 delete s; 74 return; 75 } 76 else if(!p->rchild) //被删结点*p无右子树,只需重接其左子树 77 { 78 q=p; p=p->lchild; delete q; 79 } 80 else if(!p->lchild) //被删结点*p无左子树,只需重接其右子树 81 { 82 q=p; p=p->rchild; delete q; 83 } 84 /*---将p所指的子树挂接到其双亲结点*f相应的位置---------*/ 85 if(!f) T=p; //被删结点为根结点 86 else if(q=f->lchild) f->lchild=p; //挂接到*f的左子树位置 87 else f->rchild=p; //挂接到*f的右子树位置 88 delete q; 89 }
查找的时间复杂度为 O(log2n),而插入和删除的基本操作也是查找,时间复杂度均为O(log2n)。创建的时间复杂度为 O(nlog2n)。
查找性能分析:
平均查找长度和二叉树的形态有关,即
最好:log2n(形态匀称,与二分查找的判定树相似)
最坏:(n+1)/2(单支树)
2、平衡二叉树(AVL树)
空树 Or 具有如下特征的二叉排序树:
(1)左子树和右子树的深度之差的绝对值不超过1;
(2)左子树和右子树也是平衡二叉树。
平衡因子只可能是-1、0和1。
深度和log2n是同数量级的(其中n为结点个数)。由此,其查找的时间复杂度是O(log2n)。
平衡二叉树的平衡调整方法:LL、RR、LR、RL。
对于同样的数据量,二叉查找树的深度大,而多叉树的深度小,因此多叉树适用于要求查找深度小的检索。
3、B树(多叉树):B-树和B+树
特点:平衡、有序、多路
(三)散列表的查找
散列查找法又叫杂凑法或散列法。
优点:查找速度极快O(1),查找效率与元素个数n无关。
1、术语:
散列函数(哈希函数)和散列地址:p=H(key),称这个对应关系H为散列函数,p为散列地址。H函数称为哈希函数。
散列表:一个有限连续的地址空间,用以存储按散列函数计算得到相应散列地址的数据记录。通常散列表的存储空间是一个一维数组,散列地址是数组的下标。
冲突:对不同的关键字可能得到同一散列地址。
同义词:具有相同函数值的关键字。
2、散列函数的构造方法
(1)数字分析法:适合于能预先估计出全体关键字的每一位上各种数字出现的频度。
(2)平法取中法:适合于关键字中每一位都有某些数字重复出现频度很高的现象。
(3)折叠法:适合于关键字的数字位数特别多。
(4)除留余数法:Hash(key)=key mod p(p是一个整数)
一般情况下,可以选p为小于表长的最大质数。
3、处理冲突的方法
(1)开放地址法:Hi=(H(key)+di) MOD m i=1,2,…,k(k≦m-1)
其中,H(key)为散列函数,m为散列表表长,di为增量序列。根据di取值的不同,可以分为以下3种探测方法:
A.线性探测法:di=l,2,3,…,m-1
B.二次探测法:di=1²,-1²,2²,-2²,3²,…,+k²,-k²(k≦m/2)
C.伪随机探测法:di=伪随机数序列
优点:只要散列表未填满,总能找到一个不发生冲突的地址。
缺点:会产生“二次聚集”现象。而二次探测法和伪随机探测法的优点是:可以避免“二次聚集”现象。缺点也很显然:不能保证一定找到不发生冲突的地址。
(2)链地址法(拉链法)
优点:非同义词不会冲突,无“聚集”现象;链表上结点空间动态申请,更适合于表厂不确定的情况。
4、ASL的决定因素:散列函数、处理冲突的方法和散列表的装填因子α(α=表中填入的记录数/散列表的长度)。
散列表的ASL是处理冲突方法和装载因子的函数。
哈希表的平均查找长度是α的函数,而不是n的函数。
哈希表所特有的特点:用哈希表构造查找表时,可以选择一个适当的装填因子α,使得平均查找长度限定在某个范围内。
关于哈希查找的几点结论:
1.哈希表技术具有很好的平均性能,优于一些传统的技术
2.链地址法优于开地址法
3.除留余数法作哈希函数优于其它类型函数