计算机基础数据结构讲解第一篇-顺序查找和折半查找
从现在开始我的博客讲介绍有关计算机基础之数据结构的内容,我将会把核心的内容讲解出来,希望能给大家带来帮助。
一:查找的基本概念
查找是在集合中寻找满足某种条件的数据元素的过程,分为查找成功和查找失败。
用于查找的数据元素集合称为查找表,由同一类型的数据元素组成,可以是数组或者链表。可以根据是否要动态修改查找表才能查找到元素分为静态查找表和动态查找表。
关键字是数据元素中唯一标识某元素的某个数据项的值,使用基于关键字的查找,查找结果是唯一的。
查找效率需要有一个评判标准,最通用的方法是用平均查找长度ASL评判。在查找过程中,ASL是所有查找过程中进行关键字比较次数的平均值。数学定义为:
其中,n是查找表的长度,P(i)是查找第i个数据元素的概率,一般认为每个元素的P(i)都相等,为1/n,C(i)是找到第i个数据元素所需进行比较的次数。
二:顺序查找
1.顺序查找介绍
顺序查找又称线性查找,主要用线性表进行查找,分为对一般的无序线性表的顺序查找和对按关键字有序的顺序表的顺序查找。
2.一般线性表的顺序查找
这是最简单的查找方法,基本思想是从线性表的一端开始,逐个检查关键字是否满足给定的条件,查找成功返回在线性表中位置,查找失败返回查找失败的信息。
算法结构如下:
typedef struct{
ElemType *elem; //元素存储空间的基址,建表时按实际长度分配,0号位留空放哨兵
int TableLen; //表的长度
}SSTable
int Search_Seq(SSTable ST,ElemType Key){
ST.elem[0] = key; //哨兵
for(i = ST.TableLen;ST.elem[i] != key;--i); //从后往前找
return i; //不存在关键字为key的元素,i为0时退出for循环
}
将ST.elem[0]称为"哨兵",目的是使得算法里循环不必判断数组是否越界。当满足i == 0时可以直接跳出,避免不需要的判断语句,提高程序效率。
平均查找长度分析:有n个元素的线性表,当定位到第i个元素时,需要进行n-i+1次关键字的比较(从后往前比较),则C(i)=n-i+1。查找成功时,当每个P(i)都相等,为1/n时,顺序查找的平均查找长度为:
查找失败时,与各关键字的比价次数是n+1次,则ASL(不成功)=n+1。
由上面的分析可以知道,顺序表的优点是对数据存储没有要求,顺序链表和链式链表皆可,对表中记录的有序性也没有要求。缺点是当n比较大时,效率比较慢,ASL比较大。
3.有序表的顺序查找
有序表的顺序查找成功查找时和一般顺序表的查找一样,但是查找失败时,因为在查找之前就知道表是关键字有序的,则查找失败时可以不用再比较表的另一端就能返回查找失败的信息,提高效率,降低ASL。
这里只分析查找失败的ASL,查找成功时的ASL和前面的一样。查找失败时,查找指针一定走到了某个失败结点,是我们虚构的空结点,实际上并不存在。所以查找结点失败时所查找的长度等于它上面的圆形结点所在的层数,则ASL为:
(1+2+3+4+..+n+n)÷(n+1) = n/2 + n/n+1
其中q(j)是到达第j个失败结点的概率,相等查找概率的情形下为1/(n+1),l(j)是第j个失败结点所在的层数。
三:折半查找
1.折半查找介绍
折半查找又称二分查找,用二分法进行查找,仅适用于有序的顺序表,不能用于链表。
2.折半查找基本思想
首先用要查找的关键字k与中间位置的结点的关键字相比较,这个中间结点把线性表分成了两个子表,若比较结果相等则查找完成;若不相等,再根据k与该中间结点关键字的比较大小确定下一步查找哪个子表,这样递归进行下去,直到找到满足条件的结点或者该线性表中没有这样的结点。查找成功,返回查找的元素;查找不成功,返回查找失败的信息。
折半查找有非递归和递归两种方法。
3.折半查找非递归算法
int Binary_Search(SeqList L,ElemType key){
int low = 0,high = L.TableLen - 1,mid;
while(low <= high){
mid = (low + high) / 2
if(L.elem[mid] == key)
return mid;
else if(L.elem[mid] > key)
high = mid - 1;
else
low = mid + 1;
}
return -1;
}
4.折半查找递归算法
int Binary_Search(SeqList L,ElemType key,int low,int high){
mid = (low + high) / 2
if(low <= high){
if(L.elem[mid] == key)
return mid;
else if(L.elem[mid] > key)
return Binary_Search(L,key,low,mid - 1);
else
return Binary_Search(L,key,mid + 1,high);
}
return -1;
}
// di一次传入的数据,这里*L指向顺序表,也可以不加*,要具体而定
Binary_Search(*L,key,0,*L.TableLen - 1)
4.折半查找判定树
折半查找可以用一个平衡二叉树来描述,称为判定树,如下:
书中的圆形结点表示一个记录,结点中的值为该记录的关键字值;树中最下面的叶节点都是方形的,表示查找不成功的情况。从中可以看到,查找成功的长度是从根节点到目的圆结点的路径上的结点数,而查找不成功时的查找长度为从根节点到对应失败结点的父结点的路径上的结点数。这个二叉树按中序遍历元素逐步递增。若序列有n个元素,则对应的判定树有n个圆形的非叶节点和n+1个方形的叶结点。
5.折半查找的ASL
用折半查找查找到给定值的比较次数不会超过树的高度,在等概率查找时,查找成功的平均查找长度为:
其中,h是树高,并且元素个数为n时树高h为 ,所以折半查找成功的时间复杂度为 ,一般情况下比顺序查找的效率高。
查找失败的时候,则计算失败结点父结点的高度,然后ASL为:
每一层失败结点的父结点高度*失败节点个数之和与失败结点总数之差。
6.折半查找的特性
折半查找需要线性表具有随机存取的特性,则只适用于顺序存储结构,且要求元素按关键字有序排列。
四:分块查找
1.介绍
分块查找又称索引顺序查找,分块查找是折半查找和顺序查找的一种改进方法,折半查找虽然具有很好的性能,但其前提条件时线性表顺序存储而且按照关键码排序,这一前提条件在结点树很大且表元素动态变化时是难以满足的。而顺序查找可以解决表元素动态变化的要求,但查找效率很低。分块查找吸收了顺序查找和折半查找各自的优点,既有动态结构,又适合于快速查找。
2.基本思想
分块查找要求把一个大的线性表分解成若干块,每块内的节点可以任意存放,但块与块之间必须排序。此外,还要建立一个索引表,把每块内的最大关键码值作为索引表的关键码值,按块的顺序存放到一个辅助数组中。查找时,首先在索引表中进行查找,确定要找的节点所在的块。由于索引表是排序的,因此,对索引表的查找可以采用顺序查找或折半查找;然后,在相应的块内采用顺序查找,即可找到对应的节点。
3.分块查找结构
4.分块查找ASL
分块查找的平均查找长度是索引查找和块内查找平均长度之和。若一个查找表长度为n,均匀分为b块,每块有s个记录,在等概率情况下,块内和索引表中均采用顺序查找,则ASL为:
ASL=(b+1)/2+(s+1)/2
此时,若s=√n,则平均查找长度取最小值√n+1 。
若对索引表进行折半查找时,则平均查找长度为:
ASL=(s+1)/2+log2(b+1)-1
这里要注意,log2(b+1)取最大值上限。