查找--有序表查找(折半查找,插值查找,斐波拉契查找)

版权声明:本文源自简书tianma,转载请务必注明出处:http://www.jianshu.com/p/a3bda7ba96ea

引言
如果待查找的数组是有序的,那么此时的查找就是有序表查找,这对于查找的帮助是很大的。属于有序表查找的有:折半查找(二分查找)、插值查找以及斐波那契查找。

1. 折半查找
折半查找又称为二分查找,是一种效率较高的查找算法。折半查找的先决条件是查找表中的数据元素排列必须是有序的。折半查找先以有序数列的中点位置为比较对象,如果要找的元素值小于中点位置元素,则将待查序列缩小为左半部分,否则为右半部分。通过一次比较,可以将查找的区间缩小一半,每次比较,都可以将当前查找范围缩小至一般,可以明显的减少比较的次数,提高查找效率。
时间复杂度:O(logn)
算法实现:

// 定义接口
interface Searcher {
    /**
     * 从数组array中查找关键字key,如果存在则返回该关键字在数组中任意出现的位置(不局限于首次或者末次之类的),否则返回-1
     */
    int search(int[] array, int key);
}

/**
 * 二分法查找,时间复杂度O(logn)
 */
class BinarySearcher implements Searcher {
    // 二分法查找前提,查找表array是顺序(这里要求递增)排列的
    @Override
    public int search(int[] array, int key) {
        int low, high, mid;
        low = 0; // 定义最低下标为array首位
        high = array.length - 1; // 定义最高下标为array末位
        while (low <= high) {
            mid = (low + high) / 2; // 折半
            if (array[mid] > key) {
                // 中值比key大,则high=mid-1
                high = mid - 1;
            } else if (array[mid] < key) {
                // 中值比key小,则low=mid+1
                low = mid + 1;
            } else {
                // 相等说明mid即为key在array中所在位置
                return mid;
            }
        }
        return -1;
    }
}

2. 插值查找
插值查找是二分查找演化而来,相比于二分查找(折半),该算法考虑的是每次折的时候折多少,即不一定是1/2;如在一本字典中找"abstract"这个单词,我们自己来操作肯定是先翻到字典开始的那一小部分,而不是从字典的中间开始进行折半查找。

在二分查找中mid=(low+high)/2=low+1/2*(high-low),插值查找就是对1/2(系数,或者说比例)进行改变,它将1/2变成 (key - array[low])/(array[high] - array[low]),其实就是计算线性比例。

时间复杂度:O(logn)
因为插值查找是依赖线性比例的,如果当前数组分布不是均匀的,那么该算法就不合适。

算法实现:

class InterpolateSearcher implements Searcher {

    @Override
    public int search(int[] array, int key) {
        int low, high, mid;
        low = 0; // 定义最低下标为array首位
        high = array.length - 1; // 定义最高下标为array末位
        while (low <= high) {
            // 相比二分法查找的更改处
            mid = low + (int) (1.0 * (key - array[low]) / (array[high] - array[low]) * (high - low));
            if (array[mid] > key) {
                // 中值比key大,则high=mid-1
                high = mid - 1;
            } else if (array[mid] < key) {
                // 中值比key小,则low=mid+1
                low = mid + 1;
            } else {
                // 相等说明mid即为key在array中所在位置
                return mid;
            }
        }
        return -1;
    }
}

3. 斐波那契查找
根据前面二分查找以及插值查找来看,有序表上的查找的关键就是如何分割当前查找的区域(二分查找对半分割,差值查找按线性比例分割),说到分割,还有一个著名的分割方式就是黄金分割。

斐波那契数列,又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、····,在数学上,斐波那契被递归方法如下定义:F(1)=1,F(2)=1,F(n)=f(n-1)+F(n-2) (n>=2)。该数列越往后相邻的两个数的比值越趋向于黄金比例值(0.618)

所以我们可以根据斐波那契数列对当前区域进行分割 :)

查找算法:在斐波那契数列找一个等于略大于查找表中元素个数的数F(n),将原查找表扩展为长度为F(n)(如果要补充元素,则补充重复最后一个元素,直到满足数组元素个数为F(n)个元素),完成后进行斐波那契分割,即F(n)个元素分割为前半部分F(n-1)个元素,后半部分F(n-2)个元素,找出要查找的元素在那一部分并递归,直到找到。
时间复杂度:O(logn),平均性能优于二分查找。
算法实现:

class FibonacciSearcher implements Searcher {

    private static final int MAX_ARRAY_SIZE = 30;

    /**
     * 得到长度为len的斐波那契数列
     * 
     * @return
     */
    private int[] fibonacci(int len) {
        if (len < 0)
            throw new IllegalArgumentException("length must bigger than 0");
        int[] fibonacci = new int[len];
        fibonacci[0] = 1;
        fibonacci[1] = 1;
        for (int i = 2; i < len; i++) {
            fibonacci[i] = fibonacci[i - 1] + fibonacci[i - 2];
        }
        return fibonacci;
    }

    @Override
    public int search(int[] array, int key) {
        int low = 0; // 低位
        int len = array.length;
        int high = len - 1; // 高位
        int mid; // 中间位
        int k = 0; // 斐波那契数列下标(用于进行分割)
        // 获取斐波那契数列
        int[] fib = fibonacci(MAX_ARRAY_SIZE);
        // 获取斐波那契数列分割点位置
        while (len > fib[k] - 1) {
            k++;
        }
        // 创建临时数组(数组长度为fib[k] - 1)
        int[] tmp = new int[fib[k] - 1];
        // 拷贝原数组到tmp数组中
        System.arraycopy(array, 0, tmp, 0, len);
        // 填充tmp数组中剩余的位置,补充的元素值为最后一个元素值
        for (int i = len; i < fib[k] - 1; i++) {
            tmp[i] = array[high];
        }

        // 开始进行类似于二分查找的查找
        while (low <= high) {
            // 对于tmp数组,整个数组的长度为fib[k]-1
            // 而 fib[k]-1 = (fib[k-1]-1) + 1 + (fib[k-2]-1);
            // 所以可以这样理解: mid下标对应元素可以将整个数组拆分为两部分,第1部分有fib[k-1]-1个元素,第2部分有fib[k-2]-1个元素
            // mid=low+fib[k-1]-1; 正是将 数组的[low, max(high,tmp.length-1)]
            // 部分按照斐波那契规则分为两部分
            mid = low + fib[k - 1] - 1;
            if (tmp[mid] > key) {
                // 需要查找第1部分
                high = mid - 1;
                // fib[k] = fib[k-1] + fib[k-2]
                // 第一部分有fib[k-1]个元素,所以将k-1赋值为k
                k = k - 1;
            } else if (tmp[mid] < key) {
                // 需要查找第2部分
                low = mid + 1;
                // fib[k] = fib[k-1] + fib[k-2]
                // 第二部分有fib[k-2]个元素,所以将k-2赋值给k
                k = k - 2;
            } else {
                // 查找成功
                // 以下代码其实就是返回 min(mid, high);
                // return Math.min(mid, high);
                if (mid <= high)
                    return mid;
                else
                    return high; // 因为mid可能大于high,即查找到了补充的元素,那么还是应该返回high
            }
        }
        return -1;
    }
}

结束语
以上三种查找算法中,都依赖于顺序表,三者的区别本质上就是分割点选的不同。在分割点的选择中,折半查找 mid=(low+high)/2是加法与除法运算;插值查找mid = low+(key-array[low])/(array[high]-array[low])*(high-low)是复杂的四则运算;斐波那契查找mid=low+fib[k-1]-1是简单的加减运算。在海量数据查找过程中细微的差别会影响最终的效率。

三种查找算法,各有优劣,实际开发可以根据数据的特点综合考虑再做出选择。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容