1. 程式人生 > >【演算法】尋找第k大的數

【演算法】尋找第k大的數

目錄:
1、引子
2、排序解決法
3、類快排解法
4、最小堆解法

1、引子

日常編碼中,常見遇到這樣的問題,“尋找最大的數”,此問題非常容易,可暴力直接遍歷找出,也可使用分冶策略找出最大值(詳見分冶演算法)。

本文中需要尋找第k大的數,筆者目前想到3個方法可解決它。

2、排序解決法

如果是一個有序陣列,那麼尋找第k的大數則相當簡單了,且效率為1。陣列排序演算法中相對較優的演算法為快速排序,效率為N*lgN,將陣列從到到小排列,第k大的數則為array[k-1]。

快排的思想為,從陣列中取任意一個值key,將大於key的值放在key右邊,小於key的值放在key左邊。key的左邊和右邊則都是有序的了,然後遞迴key左邊的子陣列和key右邊的子陣列,直到每個子陣列長度為1,此時,整個陣列均有序了。

程式碼如下

public static int partition(int[] array, int left, int right) {
    int k = array[left];
    int i = left;
    int j = right;
    while (j > i) {
        while (array[j] < k && j > i) {
            j--;
        }
        if (j > i) {
            array[i] = array[j];
            i++;
        }
        while (array[i] > k && j > i) {
            i++;
        }
        if (j > i) {
            array[j] = array[i];
            j--;
        }
    }
    array[i] = k;
    return i;
}

public static void quickSort(int[] array, int left, int right) {
    if (left >= right) {
        return;
    }
    int i = partition(array, left, right);
    quickSort(array, left, i - 1);
    quickSort(array, i + 1, right);
}

本文中快排略有差異,是按從大到小順序排列。
快排的partition演算法有兩種寫法,具體可檢視快速排序及主定理。此解法效率為N*lgN

3、類快排解法

由於只要求找出第k大的數,沒必要將陣列中所有值都排序。

快排中的partition演算法,返回key在陣列中的位置,如果key的位置正好等於k-1,那麼問題則得到解決,如果key的位置不等於k-1,可使用遞迴查詢對應子陣列。直到key的位置等於k-1,則找對問題的解。

public static int findK(int[] array, int left, int right, int k) {
    int i = partition(array, left, right);
    if (i == k - 1) {
        return array[k - 1];
    } else if (i > k - 1) {
        return findK(array, left, i - 1, k);
    } else if (i < k - 1) {
        return findK(array, i + 1, right, k);
    }
    return 0;
}

此解法的效率值為N*lgK,由於K是常數,所以此解法效率值為N,優於排序解法

4、最小堆解法

最小堆是一種特殊的陣列結構,它實質是一個完全二叉樹,且樹中子節點的值均大於父節點的值,詳見 堆排序及優先佇列

考慮到只需要找到第k大的數,構造一個大小為k的最小堆,堆中根節點為最小值。如果陣列中最大的幾個數均在堆中,那麼堆中根節點的值就是問題的解。

構造最小堆

public static void maxHeapify(int[] array, int size, int i) {
    int left = 2 * i + 1;
    int right = 2 * i + 2;
    int small = i;
    if (left < size) {
        if (array[small] > array[left]) {
            small = left;
        }
    }
    if (right < size) {
        if (array[small] > array[right]) {
            small = right;
        }
    }
    if (small != i) {
        int temp = array[small];
        array[small] = array[i];
        array[i] = temp;
        maxHeapify(array, size, small);
    }
}

public static void buildHeap(int[] array, int size) {
    for (int i = size - 1; i >= 0; i--) {
        maxHeapify(array, size, i);
    }
}

最小堆已構造完成,將陣列中剩餘的值與根節點相比,大於根節點的值則將根節點的值與之交換,同時維護最小堆的特性,遍歷結束,則根結點即為問題的解。

public static int findKByHeap(int[] array, int k) {
    buildHeap(array, k);
    for (int i = k + 1; i < array.length; i++) {
        if (array[i] > array[0]) {
            int temp = array[i];
            array[i] = array[0];
            array[0] = temp;
            maxHeapify(array, k, 0);
        }
    }
    return array[0];
}

程式碼地址:https://github.com/okunu/DataStructure ,歡迎訪問本人的github



作者:某昆
連結:https://www.jianshu.com/p/33ee33ce8699
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。