1. 程式人生 > >減治法在查詢演算法中的應用(JAVA)--快速查詢

減治法在查詢演算法中的應用(JAVA)--快速查詢

減治法在查詢演算法中的應用

快速查詢:選擇問題是求一個n個數列表的第k個最小元素的問題,這個數k被稱為順序統計量。對於k=1或k=n來說,這並沒有什麼意義,我們通常會要找出這樣的元素:該元素比列表中一半元素大,比另一半元素小,這樣的元素被稱為中值。我們當然可以對列表進行排序,之後找出對應下標的值,但是!!!這樣一個查詢問題,反而要對整個列表排序,是不是有點多餘了呢?

這裡引入劃分的概念我們可以標定一個樞軸(任意元素,一般為首個元素),使得左半部分元素均小於樞軸,右半部分均大於樞軸。劃分的方法由兩種,Lomuto劃分和Hoare劃分。這裡僅介紹Lomuto。

我們假設有一個數組a[0, n-1],其子陣列為a[l, r](0 <= l <= r <= n-1),假定首個元素為樞軸p,將該陣列分為三段,順序放在p之後,依次為,第一段[元素小於p],第二段[元素大於等於p],第三段[尚未處理元素]。演算法開始時前兩段均為空。

從i = l+1開始,從左到右掃描子陣列a[l, r],將第三段的首個元素與p比較,若a[i]>=p,執行i+1,這就相當於將a[i]劃入了第二段,同時縮小了第三段;若a[i]<p,需要將s+1(s始終指向第一段的末位元素),同時交換a[i]與a[s],之後i+1。直到第三段為空,交換a[p]與a[s]。

下圖為Lomuto劃分示意圖:


熟悉快速排序的讀者估計看出來了,這就是快速排序中的一部分函式,只不過沒有接觸過Lomuto這種叫法而已。

當然,我們這裡使用的方法就是快速選擇(“快速”這一方法一開始並非用於排序,而是查詢),下面給出查詢第k小元素的程式碼:

public class Main {
    static int[] a= {89, 45, 68, 90, 29, 34, 17};
    static int k = 2;
    public static void main(String[] args) {
        System.out.println(fastsort(0, a.length-1, k));

        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
    }

    private static int Lomuto(int l, int r) {
        int p = a[l];
        int s = l;
        for (int i = l+1; i <= r; i++) {
            if (a[i] < p) {
                s = s+1;
                int temp = a[s];
                a[s] = a[i];
                a[i] = temp;
            }
        }
        int temp = a[l];
        a[l] = a[s];
        a[s] = temp;
        return s;
    }
    private static int fastsort(int l, int r, int k) {
        int s = Lomuto(l, r);
        /**
         * s在劃分之後變成了樞軸所在的位置下標,如果s=k,輸出a[s]
         * 這裡要寫成l+k-1,如果劃分到右側,只寫k會出問題
         * */
        if (s == l + k - 1) {
            return a[s];
        }else if (s > l + k - 1){
            return fastsort(l, s-1, k);
        } else {
            return fastsort(s+1, r, l+k-1-s);
        }
    }
}
不幸的是,這樣的演算法時間複雜度為O(n^2),比之前基於排序的方法實際上更糟糕,但是分析表明,這種方法的平均情況下效率是線性的。而且基於劃分的演算法不僅可以查詢第k小的元素,還可以給出列表中k個最小元素和n-k個最大元素。