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

版權(quán)聲明:本文源自簡書tianma,轉(zhuǎn)載請(qǐng)務(wù)必注明出處:http://www.lxweimin.com/p/a3bda7ba96ea

引言
如果待查找的數(shù)組是有序的,那么此時(shí)的查找就是有序表查找,這對(duì)于查找的幫助是很大的。屬于有序表查找的有:折半查找(二分查找)、插值查找以及斐波那契查找。

1. 折半查找
折半查找又稱為二分查找,是一種效率較高的查找算法。折半查找的先決條件是查找表中的數(shù)據(jù)元素排列必須是有序的。折半查找先以有序數(shù)列的中點(diǎn)位置為比較對(duì)象,如果要找的元素值小于中點(diǎn)位置元素,則將待查序列縮小為左半部分,否則為右半部分。通過一次比較,可以將查找的區(qū)間縮小一半,每次比較,都可以將當(dāng)前查找范圍縮小至一般,可以明顯的減少比較的次數(shù),提高查找效率。
時(shí)間復(fù)雜度:O(logn)
算法實(shí)現(xiàn):

// 定義接口
interface Searcher {
    /**
     * 從數(shù)組array中查找關(guān)鍵字key,如果存在則返回該關(guān)鍵字在數(shù)組中任意出現(xiàn)的位置(不局限于首次或者末次之類的),否則返回-1
     */
    int search(int[] array, int key);
}

/**
 * 二分法查找,時(shí)間復(fù)雜度O(logn)
 */
class BinarySearcher implements Searcher {
    // 二分法查找前提,查找表array是順序(這里要求遞增)排列的
    @Override
    public int search(int[] array, int key) {
        int low, high, mid;
        low = 0; // 定義最低下標(biāo)為array首位
        high = array.length - 1; // 定義最高下標(biāo)為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. 插值查找
插值查找是二分查找演化而來,相比于二分查找(折半),該算法考慮的是每次折的時(shí)候折多少,即不一定是1/2;如在一本字典中找"abstract"這個(gè)單詞,我們自己來操作肯定是先翻到字典開始的那一小部分,而不是從字典的中間開始進(jìn)行折半查找。

在二分查找中mid=(low+high)/2=low+1/2*(high-low),插值查找就是對(duì)1/2(系數(shù),或者說比例)進(jìn)行改變,它將1/2變成 (key - array[low])/(array[high] - array[low]),其實(shí)就是計(jì)算線性比例。

時(shí)間復(fù)雜度:O(logn)
因?yàn)椴逯挡檎沂且蕾嚲€性比例的,如果當(dāng)前數(shù)組分布不是均勻的,那么該算法就不合適。

算法實(shí)現(xiàn):

class InterpolateSearcher implements Searcher {

    @Override
    public int search(int[] array, int key) {
        int low, high, mid;
        low = 0; // 定義最低下標(biāo)為array首位
        high = array.length - 1; // 定義最高下標(biāo)為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. 斐波那契查找
根據(jù)前面二分查找以及插值查找來看,有序表上的查找的關(guān)鍵就是如何分割當(dāng)前查找的區(qū)域(二分查找對(duì)半分割,差值查找按線性比例分割),說到分割,還有一個(gè)著名的分割方式就是黃金分割。

斐波那契數(shù)列,又稱黃金分割數(shù)列,指的是這樣一個(gè)數(shù)列:1、1、2、3、5、8、13、21、····,在數(shù)學(xué)上,斐波那契被遞歸方法如下定義:F(1)=1,F(xiàn)(2)=1,F(xiàn)(n)=f(n-1)+F(n-2) (n>=2)。該數(shù)列越往后相鄰的兩個(gè)數(shù)的比值越趨向于黃金比例值(0.618)

所以我們可以根據(jù)斐波那契數(shù)列對(duì)當(dāng)前區(qū)域進(jìn)行分割 :)

查找算法:在斐波那契數(shù)列找一個(gè)等于略大于查找表中元素個(gè)數(shù)的數(shù)F(n),將原查找表擴(kuò)展為長度為F(n)(如果要補(bǔ)充元素,則補(bǔ)充重復(fù)最后一個(gè)元素,直到滿足數(shù)組元素個(gè)數(shù)為F(n)個(gè)元素),完成后進(jìn)行斐波那契分割,即F(n)個(gè)元素分割為前半部分F(n-1)個(gè)元素,后半部分F(n-2)個(gè)元素,找出要查找的元素在那一部分并遞歸,直到找到。
時(shí)間復(fù)雜度:O(logn),平均性能優(yōu)于二分查找。
算法實(shí)現(xiàn):

class FibonacciSearcher implements Searcher {

    private static final int MAX_ARRAY_SIZE = 30;

    /**
     * 得到長度為len的斐波那契數(shù)列
     * 
     * @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; // 斐波那契數(shù)列下標(biāo)(用于進(jìn)行分割)
        // 獲取斐波那契數(shù)列
        int[] fib = fibonacci(MAX_ARRAY_SIZE);
        // 獲取斐波那契數(shù)列分割點(diǎn)位置
        while (len > fib[k] - 1) {
            k++;
        }
        // 創(chuàng)建臨時(shí)數(shù)組(數(shù)組長度為fib[k] - 1)
        int[] tmp = new int[fib[k] - 1];
        // 拷貝原數(shù)組到tmp數(shù)組中
        System.arraycopy(array, 0, tmp, 0, len);
        // 填充tmp數(shù)組中剩余的位置,補(bǔ)充的元素值為最后一個(gè)元素值
        for (int i = len; i < fib[k] - 1; i++) {
            tmp[i] = array[high];
        }

        // 開始進(jìn)行類似于二分查找的查找
        while (low <= high) {
            // 對(duì)于tmp數(shù)組,整個(gè)數(shù)組的長度為fib[k]-1
            // 而 fib[k]-1 = (fib[k-1]-1) + 1 + (fib[k-2]-1);
            // 所以可以這樣理解: mid下標(biāo)對(duì)應(yīng)元素可以將整個(gè)數(shù)組拆分為兩部分,第1部分有fib[k-1]-1個(gè)元素,第2部分有fib[k-2]-1個(gè)元素
            // mid=low+fib[k-1]-1; 正是將 數(shù)組的[low, max(high,tmp.length-1)]
            // 部分按照斐波那契規(guī)則分為兩部分
            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]個(gè)元素,所以將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]個(gè)元素,所以將k-2賦值給k
                k = k - 2;
            } else {
                // 查找成功
                // 以下代碼其實(shí)就是返回 min(mid, high);
                // return Math.min(mid, high);
                if (mid <= high)
                    return mid;
                else
                    return high; // 因?yàn)閙id可能大于high,即查找到了補(bǔ)充的元素,那么還是應(yīng)該返回high
            }
        }
        return -1;
    }
}

結(jié)束語
以上三種查找算法中,都依賴于順序表,三者的區(qū)別本質(zhì)上就是分割點(diǎn)選的不同。在分割點(diǎn)的選擇中,折半查找 mid=(low+high)/2是加法與除法運(yùn)算;插值查找mid = low+(key-array[low])/(array[high]-array[low])*(high-low)是復(fù)雜的四則運(yùn)算;斐波那契查找mid=low+fib[k-1]-1是簡單的加減運(yùn)算。在海量數(shù)據(jù)查找過程中細(xì)微的差別會(huì)影響最終的效率。

三種查找算法,各有優(yōu)劣,實(shí)際開發(fā)可以根據(jù)數(shù)據(jù)的特點(diǎn)綜合考慮再做出選擇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容