什么是顺序查找呢?顺序查找的原理很简单,就是遍历整个列表,逐个进行记录的关键字与给定值比较,若某个记录的关键字和给定值相等,则查找成功,找到所查的记录。如果直到最后一个记录,其关键字和给定值比较都不等时,则表中没有所查的记录,查找失败。
在前面讲解了顺序表的存储结构,针对顺序查找,即适用于线性表的顺序存储结构,也适用于线性表的链式存储结构。其中针对顺序查找有个关键点:哨兵。
哨兵:为了在for循环中设置循环的停止条件,避免全部循环。
#include<stdio.h> int Search_seq(int R[],int length,int key) { int i; R[0]=key; for(i=length-1;R[i]!=key;--i);//找不到时i为0,否则R[i]就是要找的结点; return i; } int main() { int R[11]={0,13,29,18,27,6,15,34,33,2,1}; int k=Search_seq(R,11,15); printf("%d ",k); getchar(); }
成功时的顺序查找的平均查找长度:
在等概率情况下,pi=1/n(1≤i≤n),故成功的平均查找长度为
(n+…+2+1)/n=(n+1)/2
即查找成功时的平均比较次数约为表长的一半。
若K值不在表中,则须进行n+1次比较之后才能确定查找失败。最终的算法复杂度为O(n)。
顺序查找的优点
算法简单,且对表的结构无任何要求,无论是用向量还是用链表来存放结点,也无论结点之间是否按关键字有序,它都同样适用。
顺序查找的缺点
查找效率低,因此,当n较大时不宜采用顺序查找。
其实顺序查找只要掌握哨兵的应用就可以了。
二、二分查找
二分查找又称折半查找,是一种效率很高的查找方法。二分查找要求:线性表是有序表,即表中结点按关键字有序排列,并且用向量作为表的存储结构,例如递增有序的数组。
1、二分查找基本思想
① low=1;high=length; // 设置初始区间
② 当low>high 时,返回查找失败信息// 表空,查找失败
③ low≤high,mid=(low+high)/2; //确定该区间的中点位置
a. 若kx<tbl.elem[mid].key,high = mid-1;转② // 查找在左半区进行
b. 若kx>tbl.elem[mid].key,low = mid+1; 转② // 查找在右半区进行
c. 若kx=tbl.elem[mid].key,返回数据元素在表中位置// 查找成功
2、代码
int BinSearch(int R[],int length,int key) { int low=1; int high=length; int mid; while(low<=high)//要加上=,因为这样指针先可以指到最后一个数0 1 2 4 比如找4 ,第一次low=1 high=3;第二次mid+1=3,这样low==high /2 指到最后 { mid=(low+high)/2; if(key==R[mid]) return mid; else if(key>R[mid]) low=mid+1;//继续在R[mid+1..high]中查找 else high=mid-1; //继续在R[low..mid-1]中查找 } return 0;//当low>high时表示查找区间为空,查找失败 }
3、性能分析
平均查找长度 = log2(n+1)-1.从折半查找过程看,以表的中点为比较对象,并以中点将表分割为两个子表,对定位到的子表继续这种操作.很类似二叉树查找。
(7,14,18,21,23,29,31,35,38,42,46,49,52)折半查找的判定树,可以看到,查找表中任一元素的过程,即是判定树中从根到该元素结点路径上各结点关键码的比较次数,也即该元素结点在树中的层次数。对于n 个结点的判定树,树高为k,则有2k-1 -1<n≤2k-1,即k-1<log2(n+1)≤k,所以k=log2(n+1) 。时间复杂度为O(logn)。
虽然折半查找的效率高,但是要将表按关键字排序。而排序本身是一种很费时的运算,所以二分法比较适用于顺序存储结构。为保持表的有序性,在顺序结构中插入和删除都必须移动大量的结点。因此,折半查找特别适用于那种一经建立就很少改动而又经常需要查找的线性表。
三、插分查找
在一个0~10000之间的100个元素从小到大均匀分布的数组中查找5,我们自然会考虑从数组下标较小的开始查找,因此二分查找还有提高空间。
mid = (low+high)/2 = low+1/2(high-low)。在这公式中我们将这个1/2进行修改:
mid = low + (key-a[low])/a[high]-a[low](high-low)。
比如在a[11] = {0,1,16,4,35,47,59,62,73,88,99},查找16,按照原来二分查找需要四次,改进后为:二次。其中mid= 1+ (16-1)/(99-1) *(10-1) = 2.377.针对代码只需将:
mid = low + (high-low)*(key-a[low])/(a[high]-a[low])即可。
加入数组分布不均匀,用插值查找未必是合适的选择。如{0,1,2,2000,2001….}。
四、斐波那契查找
斐波那契查找与折半查找很相似,他是根据斐波那契序列的特点对有序表进行分割的。他要求开始表中记录的个数为某个斐波那契数小1,及n=Fk-1;
1、算法思想
斐波那契查找的核心是:
1)当key=a[mid]时,查找成功;
2)当key<a[mid]时,新的查找范围是第low个到第mid-1个,此时范围个数为F[k-1] - 1个,即数组左边的长度,所以要在[low, F[k - 1] - 1]范围内查找;
3)当key>a[mid]时,新的查找范围是第mid+1个到第high个,此时范围个数为F[k-2] - 1个,即数组右边的长度,所以要在[F[k - 2] - 1]范围内查找。
关于斐波那契查找, 如果要查找的记录在右侧,则左侧的数据都不用再判断了,不断反复进行下去,对处于当众的大部分数据,其工作效率要高一些。所以尽管斐波那契查找的时间复杂度也为O(logn),但就平均性能来说,斐波那契查找要优于折半查找。可惜如果是最坏的情况,比如这里key=1,那么始终都处于左侧在查找,则查找效率低于折半查找。
还有关键一点,折半查找是进行加法与除法运算的(mid=(low+high)/2),插值查找则进行更复杂的四则运算(mid = low + (high - low) * ((key - a[low]) / (a[high] - a[low]))),而斐波那契查找只进行最简单的加减法运算(mid = low + F[k-1] - 1),在海量数据的查找过程中,这种细微的差别可能会影响最终的效率。
2、代码
// 斐波那契查找.cpp #include "stdafx.h" #include <memory> #include <iostream> using namespace std; const int max_size=20;//斐波那契数组的长度 /*构造一个斐波那契数组*/ void Fibonacci(int * F) { F[0]=0; F[1]=1; for(int i=2;i<max_size;++i) F[i]=F[i-1]+F[i-2]; } /*定义斐波那契查找法*/ int Fibonacci_Search(int *a, int n, int key) //a为要查找的数组,n为要查找的数组长度,key为要查找的关键字 { int low=0; int high=n-1; int F[max_size]; Fibonacci(F);//构造一个斐波那契数组F int k=0; while(n>F[k]-1)//计算n位于斐波那契数列的位置 ++k; int * temp;//将数组a扩展到F[k]-1的长度 temp=new int [F[k]-1]; memcpy(temp,a,n*sizeof(int)); for(int i=n;i<F[k]-1;++i) temp[i]=a[n-1]; while(low<=high) { int mid=low+F[k-1]-1; if(key<temp[mid]) { high=mid-1; k-=1; } else if(key>temp[mid]) { low=mid+1; k-=2; } else { if(mid<n) return mid; //若相等则说明mid即为查找到的位置 else return n-1; //若mid>=n则说明是扩展的数值,返回n-1 } } delete [] temp; return -1; } int _tmain(int argc, _TCHAR* argv[]) { int a[] = {0,16,24,35,47,59,62,73,88,99}; int key=100; int index=Fibonacci_Search(a,sizeof(a)/sizeof(int),key); cout<<key<<" is located at:"<<index; system("PAUSE"); return 0; }