查找算法(二)插值查找

二分查找(折半查找)

它的前提是线性表中的记录必须是有序的,线性表必须采用顺序存储。折半查找的基本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止。

代码实现
int BinarySearch(int a[], int value){
    int n = a.length;
    int low = 0;
    int high = n-1;
    while(low <= high){
        int mid = low + (high - low)/2;
        if(a[mid] == value){
            return mid;
        }
        elseif(a[mid] > value){
            high = mid -1;
        }
        else{
            low = mid + 1;
        }
    }
    return -1;
}

递归版本

int BinarySearch(int a[], int value, int low, int high){
    int mid = low + (high - low)/2;
    if(a[mid] == value){
        return mid;
    }
    elseif(a[mid] > value){
        return BinarySearch(a,value,low,mid - 1);
    }else{
        return BinarySearch(a,value,mid + 1,high);
    }
}

时间复杂度:O(log(n))

注:折半查找的前提条件是需要有序顺序存储,对于静态查找表,一次排序后不再变化,折半查找能得到不错的效率。但对于需要频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用了。

插值查找

基本思想:基于二分查找,将查找点的选择改进为自适应选择,可以提高查找效率。 将二分查找的插值计算公式改为

mid = low + \tfrac{(value - a[low])}{(a[high] - a[low])}(high-low)mid=low+(a[high]a[low])(valuea[low])(highlow)

时间复杂度:平均情况O(loglog(n)),最坏O(log(n))

注:对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找的平均性能比折半查找要好。

斐波那契查找

原理:斐波那契查找与折半查找很相似,他是根据斐波那契数列的特点对有序表进行分割的。他要求开始表中记录的个数比某个斐波那契数小1,即n=F(K)-1;当记录不满足时,后面的元素都赋值为最后一个值。(注:使得n=F(K)-1是因为:如果表中的数据为F(K)-1个,mid分割又用掉一个,剩下F(K)-2个,正好分给两个子序列,每个子序列的长度为F(K-1)-1和F(K-2)-1,格式和之前的统一,方便递归编程实现)

mid = low + F(k-1) - 1mid=low+F(k1)1

将value值与第mid位置的记录进行比较,比较结果分为三种:

  1. 相等,则返回mid
  2. 大于,low = mid + 1low=mid+1,k = k - 2k=k2
  3. 小于,high = mid - 1high=mid1,k = k - 1k=k1

(说明:low=mid+1说明待查找的元素在[mid+1,high]范围内,k-=2 说明范围[mid+1,high]内的元素个数为F(k-2)-1个)

代码实现
int FibonacciSearch(int[] a, int value){

    int[] F = {0,1,1,2,3,5,8,13,21,34,55};
    int n, low, high, mid, k;
    n = a.length;
    low = 1;
    high = n;
    k = 0;//斐波那契数列的下标
    while(n > F[k] - 1){//计算n位于斐波那契数列的位置
        ++k;
    }
    //省略将a后移一位,并且长度扩展到F(k)
    for(int i = n + 1; i <= F[k] - 1; i++){//将不满的数值补全,长度为F(k) - 1
        a[i] = a[n];
    }
    while(low <= high){
        mid = low + F[k - 1] - 1;
        if(value > a[mid]){
            low = mid + 1;
            k = k - 2;//mid右边的个数,即F[k-2]个
        }elseif(value < a[mid]){
            high = mid - 1;
            k = k - 1;//mid左边的个数
        }else{
            if(mid < n){
                return mid;
            }else{
                return n;
            }
        }
    }
    return -1;
}

总结: 平均性能平均性能:斐波那契>折半>插值

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

posted @ 2017-12-12 18:39  small-boy  阅读(5380)  评论(0编辑  收藏  举报