查找(顺序、折半、插值、斐波那契)
本文参考自《大话数据结构》
查找就是根据给定的某个值,在查找表中确定一个其关键字等于给定值得数据元素(或记录)
顺序查找
/* 顺序查找,a为数组,n为要查找的数组个数,key为要查找的关键字 */
int Sequential_Search(int* a, int n, int key){
int i;
for(i=1;i<=n;i++){
if(a[i] == key)
return i;
}
return 0;
}
顺序查找优化
/* 有哨兵顺序查找 */
int Sequential_Search2(int *a, int n, int key){
int i;
a[0] = key; //设置a[0]为关键字值,我们称之为“哨兵”
i = n; //循环从数组尾部开始
while(a[i] != key)
i--;
return i; //返回0则说明查找失败
}
这种在查找方向的尽头防止哨兵
免去了在查找过程中每一次比较后都要判断查找位置是否越界的小技巧,看似与原先差别不大,但是在数据多的时候,效率提高很大,是非常好的编码技巧。时间复杂度为O(n)
。
有序表查找
折半查找
折半查找(Binary Search)技术,又称为二分查找。它的前提是线性表中的记录必须是关键码有序(通常从小到大有序),线性表必须采用顺序存储 。折半查找的基本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;否则在中间记录的右半区查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止。
/* 折半查找 */
int Binary_Search(int *a, int n, int key){
int low, high, mid;
low = 1; //定义最低下标为记录首位
high = n; //定义最高下标为记录末位
while(low<=high){
mid = (low+high)/2; //折半
if(key < a[mid]) //若查找值比中值小
high = mid-1; //最高下标调整到中位下标小一位
else if(key>a[mid]) //若查找值比中值大
low = mid+1; //最低下标调整到中位下标大一位
else
return mid; //若相等则说明mid即为查找到的位置
}
return 0;
}
该算法复杂度和完全二叉树的深度**(log2(n))+1向下取整
** 一样,最坏情况下找到关键字或查找失败的次数为**(log2(n))+1向下取整
** ,最终,折半算法的时间复杂度为O(logn)
。
但是对于需要频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,不建议使用。
插值查找
折半查找的改进:
mid = (low+high)/2;
//将上面的这句改进得到:
mid = low+(high-low)*(key-a[low])/(a[high]-a[low]); //插值
改进之后得到一种新的有序表查找算法:插值查找法。插值查找是根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,其核心就在于插值的计算公式(key-a[low])/(a[high]-a[low]) ,从时间复杂度上看,也是O(logn)
,但是对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好得多。相反,数组中分布着类似{0,1,2,2000,2001,...,999998,999999}
这种极端不均匀的数据,用插值查找不是合适的选择。
斐波那契查找
利用黄金分割原理实现的
/* 斐波那契查找 */
int Fibonacci_Search(int *a, int n, int key){
int low, high, mid, i, k;
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; //最高下标调整到分隔下标mid-1处
k = k-1; //斐波那契数列下标减一位
}else if(key > a[mid]){ //若查找记录大于当前分隔记录
low = mid+1; //最低下标调整到分隔下标mid+1处
k = k-2; //斐波那契数列下标减2位
}else{
if(mid<=n)
return mid; //若相等则说明mid即为查找到的位置
else
return n; //若mid>n说明是补全数值,返回n
}
}
return 0;
}
斐波那契查找的时间复杂度也是O(logn)
,但就平均性能来说,斐波那契查找要优于折半查找,但是如果始终处于左侧长半区在查找,那么查找效率要低于折半查找。
还有比较重要的一点,折半查找是进行加法与除法运算mid=(low+high)/2
,插值查找进行复杂的四则运算(mid=low+(high-low)*(key-a[low])/(a[high]-a[low]))
,而斐波那契查找只是最简单加减法运算mid=low+F[k-1]-1
,在海量数据的查找过程中,这些细微的差别可能会影响最终的查找效率。
斐波那契例子
#include <stdio.h>
#define MAXNUM 20 //设置斐波那契数列最大值20
int F[MAXNUM];
/* 构造斐波那契数列 */
int CreateFibonacci(){
int i;
F[0] = 0;
F[1] = 1;
for(i=2;i<MAXNUM;i++)
F[i] = F[i-1] + F[i-2];
}
/* 斐波那契查找 */
int Fibonacci_Search(int* a, int n, int key){
int i = 0;
int low, high, mid, j;
low = 1; //最低下标为记录首位
high = n; //最高下标为记录末位
while(n>F[i]-1) //计算n在斐波那契数列哪一个位置
i++;
for(j=n;j<F[i]-1;j++)
a[j] = a[n];
while(low <= high){
mid = low+F[i-1]-1; //计算分隔mid的下标
if(key > a[mid]){
low = mid+1;
i-=2; //斐波那契数列下标减2位
}else if(key < a[mid]){
high = mid-1;
i-=1; //斐波那契数列下标减1位
}else{
if(mid<=n)
return mid; //如果相等说明mid为查找到的位置
else
return n; //如果mid>n说明是补全的数值,返回n
}
}
return 0;
}
int main(){
int i;
int a[10] = {1,5,9,20,59,67,88,150,255,1024};
//构建斐波那契数列
CreateFibonacci();
// for(i=0;i<MAXNUM;i++)
// printf("%d, ",F[i]);
printf("查找成功:%d\n",a[Fibonacci_Search(a, 9, 67)]);
}