查找(顺序查找、二分查找、插值查找)

来源:互联网 发布:ug数控车编程教程视频 编辑:程序博客网 时间:2024/05/29 17:21

查找定义:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。
查找表分类:静态查找表和动态查找表。
动态查找表:在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。

1. 顺序查找

/* 顺序查找,a为数组,n为要查找的数组元素个数,key为要查找的关键字*/int Sequential_Search(int *a, int n, int key){     int i = 0;     for(; i < n; i++)     {          if(a[i] == key)               return i;     }     return 0;}/*有哨兵顺序查找-优化最简单的顺序查找*//*优化部分:a[0]存放要查找的关键字key,减少了数组越界的比较,如果查找表长度很大,还是比最简单的顺序查找快很多的。ps:a[0] = key的目的就是让上述的简单顺序查找的两次判断修正为一次a[i]与a[0]是否相等的一次判断。*/
int Sequential_Search2(int *a, int n, int key){     int i;     a[0] = key;     for(i = n; a[i] != a[0]; i--);     return i;}
  平均查找长度(Average Search Length,ASL)
     需和指定key进行比较的关键字的个数的期望值,成为查找算法在查找成功时的平均查找长度。
     对于含有n个数据元素的查找表,查找成功的平均查找长度为:ASL = Pi*Ci的和。
     Pi:查找表中第i个数据元素的概率。
     Ci:找到第i个数据元素时已经比较过的次数。
     顺序查找 查找成功时的平均查找长度为:
     (假设每个数据元素的概率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;
     当查找不成功时,需要n+1次比较,时间复杂度为O(n);


2. 有序表查找


2.1 折半查找

前提:线性表中的记录必须是关键字有序(通常从小到大),线性表必须采用顺序存储。
基本思想:取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录左半区继续查找;否则,在右半区查找。不断重复,知道查找成功或者查找失败为止。
/*折半查找,非递归*/int Binary_Search(int *a, int n, int key){     int low, high;     int mid;     low = 1;     high = n;         while(low < high)//可以有等号 low < = high ,不影响判断。     {          mid = (low + high) / 2; //可以修正为 mid = (low + high) >> 1;          if(a[mid] == key)               return mid;          if(a[mid] > key)               high = mid - 1;          if(a[mid] < key)               low = mid + 1;//对于if else语句,可以考虑条件表达式,减少代码的行数。     }     return 0;}/*折半查找,递归实现*/int Binary_Search2(int *a, int low, int high, int key){     int mid = (low + high) / 2;     if(a[mid] == key)          return mid;     if(a[mid] > key)          return Binary_Search2(a, low, mid-1, key);     //有没有return都可以。     else          return Binary_Search2(a, mid+1, high, key);     //有没有return都可以。}
/*     ASL: “具有n个节点的完全二叉树的深入为[log2n]+1,尽管折半查找判定二叉树不是完全二叉树,但同样相同的推导可以得出,
               最坏情况是查找到关键字或查找失败的次数为[log2n]+1”,最好情况是1,所以折半查找的时间复杂度为o(logN)>o(n).

     注:折半查找的前提条件是需要有序表顺序存储,对于静态查找表,一次排序后不再变化,这样的算法已经比较好了。但对于需要
     频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用。——《大话数据结构》
*/

2.2 插值查找

首先考虑一个新问题,为什么一定要是折半,而不是折四分之一或者折更多呢?
打个比方,在英文字典里面查“apple”,你下意识翻开字典是翻前面的书页还是后面的书页呢?如果再让你查“zoo”,你又怎么查?很显然,这里你绝对不会是从中间开始查起,而是有一定目的的往前或往后翻。
同样的,比如要在取值范围1 ~ 10000 之间 100 个元素从小到大均匀分布的数组中查找5, 我们自然会考虑从数组下标较小的开始查找。
经过以上分析,折半查找这种查找方式,还是有改进空间的,并不一定是折半的!
mid = (low+high)/ 2, 即 mid = low + 1/2 * (high - low);
改进为下面的计算机方案(不知道具体过程):mid = low + (key - a[low]) / (a[high] - a[low]) * (high - low),也就是将上述的比例参数1/2改进了,根据关键字在整个有序表中所处的位置,让mid值的变化更靠近关键字key,这样也就间接地减少了比较次数。

分析:从时间复杂度上来看,它也是o(n),但是对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么差值查找未必是很合适的选择。

2.3 斐波那契查找

原理:利用斐波那契数列的性质,黄金分割的原理来确定mid的位置。
代码如下:
/*斐波那契 查找*/int Fbonacci_Search(int *a, int n, int key){int low,high,mid,i,k;int F[] = {0,1,1,2,3,5,8,13,21,34};//经典的斐波那契数列已经早就定义好,也可以递归自己求解。low = 1;high = n;k = 0;while(n > F[k] - 1)//计算 n 位于斐波那契数列的位置k++;for(i=n; i<F[k] - 1; i++)//将不满的数值补全a[i] = a[n];while(low <= high){mid = low + F[k-1] - 1;//利用斐波那契数列来找寻下一个要比较的关键字的位置if(key < a[mid]){high = mid - 1;k--;}else {if(key > a[mid]){low = mid + 1;k = k -2;}else{if(mid <= n)return mid;elsereturn n;}}}}

总结:
折半查找进行加法与除法运算(mid = (low + high) / 2),插值查找进行复杂的四则运算( mid = low + (key - a[low] / (a[high] - a[low]) * (high - low)) ),二斐波那契查找只是运用简单家减法运算 (mid  = low + f[k-1] -1) ,在海量的数据查找过程中,这种席位的差别会影响最终的查找效率。三种有序表的查找本质上是分割点的选择不同,各有优劣,实际开发可根据数据的特点综合考虑再做决定。

5 0