有序表查找


好久沒上博客園了,之前說好的一周寫一個博客來記錄自己的考研計划也落空了。

忙着復習,好久都沒有打開電腦,計划也都是寫在紙上了。最新開始數據結構的復習才打開了電腦。

開始敲代碼的感覺真好。看來我注定是一個碼農了。以后還是要多敲敲代碼,畢竟是以后吃飯的家伙,三日不練,生疏啊。

不嘮叨了,說說今天要寫的主題——有序表查找。
(ps 這篇博客是查看程傑老師的大話數據結構后,參考網絡上的文章寫成的。優缺點和時間復雜度這段完全抄錄的程傑老師的原話)。

 

一、定義

就是字面上的意思,在一張有序表中進行查找。有序表是啥?就是數據按照一定順序排好的表,而不是一堆雜亂無章的數據。

有序表查找的基本前提就是數據是有序的。

 

二、幾種常見的有序表查找方法

1.折半查找

折半查找(Binary Search) 又稱為 二分查找。

折半查找的基本思想是:

將原始數據分為等份的兩部分,比較關鍵字與中間值的大小,如果關鍵字小於中間值,說明關鍵字落在左半部分,將查找范圍縮小為左半部分,繼續折半查找;
如果關鍵字大於中間值,說明關鍵字落在右半部分,將查找分為縮小為右半部分,繼續折半查找。
通過關鍵字與中間值的對比,不斷縮小查找范圍,最終查找數據。原理圖如下。

二分法的關鍵是中間值(也就是分隔)的選取。通過分隔,我們將查找區間縮小,通過不斷縮小查找范圍,來查找數據。

 

2、插值查找

二分法將空間分隔的方法非常粗糙,就是講區間折半。

考慮這樣一組數據 {0, 1, 3, 4, 5, 7, 9, 10, 12, 13, 14} ,需要查找的數據是10。

觀察這個數組,數組的前后分布存在相對均勻,如果我們使用折半查找,我們共需要查找 4 次,才能查找到數據。這就是不考慮數據分布,粗糙地選取分隔的后果。

為了改進折半查找的缺點,我們重新選取分隔。

經過算法科學家的推到,改進方案如下:

分隔的取值變成上圖第二個公式。如果使用第二個公式,上述的查找只需要 1 次就可以輕松查找到。

 

3、斐波那契查找

斐波那契查找是利用黃金分隔原理來實現的。它的本質和二分查找、插值查找沒有區別,都是通過設置分隔,不斷將區間縮小,最后查找到關鍵字的。
與之前兩個查找方法相似,斐波那契的不同也是分隔設置的不同,它是通過斐波那契數列來設置的。

斐波那契數列 F = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...}

斐波那契查找的一個限制就是,src的數據個數需要是斐波那契數列中的元素之一。
例如src = {0, 1, 16, 24, 35, 47, 59,  62, 73, 88, 99} 。
該數組的個數為11個,11並不是斐波那契數列的元素,13才是,所以需要將數組擴容到13個,即令src[11]=src[12]=99。

斐波那契查找的具體代碼如下

/**
     * 從有序表中查找數據
     * @param key 需要查找的數據
     * @param src 有序表
     * @return
     */
    public static int fobSearch(int key,int[] src){
        int length = src.length;
        int low = 0; //low high 的初始值分別等於有序表索引的最小值和最大值
        int high = length-1;         
        int fobInex = 0; // 我們后面需要用到的  斐波那契數組中的索引值
        int middle = 0 ; //二分法的分隔值
        
        //使用斐波那契查找的要求 就是有序表的元素個數必須是斐波那契數組元素的值
        while(length > getFobonacci(fobInex)){ 
            fobInex ++;
        }
        
        //如果有序表的元素個數不等於斐波那契數組元素的值,則需要在后面補全
        int newCapacity = getFobonacci(fobInex);//新數組的大小
        if(length < newCapacity){
            src = Arrays.copyOf(src, newCapacity);
            for(int i=length;i<newCapacity;i++){//將后續的數值補全
                src[i] = src[i-1];
            }
        }
        
        HelpUtils.printIntArray(src);//打印一下補全的數組
        
        while(low <= high){
            middle = low + getFobonacci(fobInex-1)-1; if (key < src[middle]) { //如果當前查找記錄小於當前分隔記錄
                high = middle - 1;
                fobInex = fobInex - 1;
            }
            else if (key > src[middle]) {
                low = middle + 1;
                fobInex = fobInex - 2;
            }
            else{
                if (middle < length) {
                    return middle; 
                }
                else{
                    return -1;
                }
            }            
        }

        return -1;
    }

可以看到分隔的選取依賴於斐波那契數列。

 

 三、優缺點和時間復雜度

二分查找、插值查找和斐波那契查找的時間復雜度都是O(logn)。

二分查找的前提條件是需要有序表順序存儲,對於靜態查找表,一次排序后不再變化,這樣的算法已經比較好了。
但是對於需要頻繁執行插入或者刪除操作的數據集來說,維護有序表的排序會帶來不小的工作量,並不適合使用。

插值查找,對於表長較大,而關鍵字分布比較均勻的查找表來說,插值查找算法的平均性能要比折半查找好得多。
反之,如果數據中分布類似{0,1,9999,999999}這樣極端不均勻的數據,用插值查找未必是最合適的選擇。

斐波那契查找,就平均性能而言,要優於二分查找,但是如果是最壞的情況,比如key=0,那么始終在左側長半區在查找,查找的效率要低於折半查找。
比較關鍵的一點是,插值、折半都需要進行比較復雜的乘除法運算,
而斐波那契只需要進行簡單的加減運算,在海量數據的查找過程中,這種細微的差別可能會影響最終的查找效率。

 

上述三個有序表查找算法的具體代碼都放在我的github上,歡迎查看。

有序表查找源碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM