1. 程式人生 > >top k演算法講解

top k演算法講解

在實際工作中,我們時常會有尋找長度為n的陣列中,排在前k的元素,對於top k的問題,最暴力的處理方式就是直接對陣列進行排序,然後再去擷取前k個數字,從而達到自己的目的,這種演算法的實現複雜度為O(nlogn),其實有O(n)的演算法或者是O(nlogk)時間複雜度的演算法。

  • 基於快排的top k演算法

    如果我們瞭解快速排序演算法的話,知道其原理是每次尋找一個數值,將陣列中所有小於這個數的值放在其左側,所有大於其數值的數放在其右側。因此呼叫一次partion之後,設其返回值為p,則比第p個數字小的所有數字在陣列的左側,比第p個數字大的所有數字都在陣列的右側。我們可以基於快排的原理用其實現尋找top k的元素。我們看下程式碼,其時間複雜度為O(n)。

    private static int partion(int[] array, int low, int high) {

        int mid = array[low];
        while (low < high) {
            while (low < high && array[high] >= mid)
                high--;
            array[low] = array[high];
            while (low < high && array
[low] <= mid) low++; array[high] = array[low]; } array[low] = mid; return low; } private static int top_k(int[] array, int k) { if (array == null || array.length == 0) return -1; if (k < 0 || k > array.length - 1
) return -1; int low = 0, high = array.length - 1; int index = partion(array, low, high); while (index != k) { if (index > k) { high = index - 1; index = partion(array, low, high); } else { low = index + 1; index = partion(array, low, high); } } return array[index]; }
  • 基於大頂堆的top k演算法
    這種演算法適合海量資料的情況下,比如我們待查詢的資料很大,甚至不可以一次全部讀入記憶體,這種方法就比較適合。下面簡單說一下其原理。
    我們先建立一個大小為k的陣列來儲存最小的k個數字,接下來我們從輸入的n個數中讀取一個數,如果容器還沒有滿則直接插入容器;若容器已經滿了,則我們此時需要將容器中最大的數字和待插入的數字做比較,如果待插入的數值小於容器中的最大值,則需要將容器中的最大值刪除,將新值插入,否則不做任何處理。
    我們通過需求分析可以發現,大頂堆可以滿足我們的需求,因此我們通過大頂堆來實現我們的容器,其程式碼如下,時間複雜度為O(nlogk)。
    private final int MAXSIZE = 10 + 1;
    private int currentSize = 1;

    private void heap_insert(int[] array, int value) {

        if (currentSize < MAXSIZE) {
            array[currentSize++] = value;
            if (currentSize == MAXSIZE) {
                for (int i = currentSize / 2; i > 0; i--) {
                    heap_adjust(array, i, currentSize);
                }
            }
        } else {
            int max = array[1];
            if (value < max) {
                array[1] = value;
                heap_adjust(array, 1, currentSize);
            }
        }
    }

    // 堆調整
    private void heap_adjust(int[] array, int s, int len) {
        int temp = array[s];
        for (int i = 2 * s; i < len; i *= 2) {
            if (i < len - 1 && array[i] < array[i + 1])
                i++;
            if (array[i] <= temp)
                break;
            array[s] = array[i];
            s = i;
        }
        array[s] = temp;

    }

另外補充一點,這個大頂堆的資料結構是我們自己來維護的,對於Java而言,其實可以直接藉助於JDK中的TreeSet集合來實現,因為TreeSet是有序的集合,其基於紅黑樹來實現。同理,對於C++來講,可以藉助set集合實現。Java基於TreeSet實現的程式碼如下:

private static TreeSet<Integer> topk(int[] array, int n) {

        TreeSet<Integer> set = new TreeSet<Integer>();
        for (int i = 0; i < array.length; i++) {

            int value = array[i];
            if (set.size() < n)
                set.add(value);
            else {
                Iterator<Integer> it = set.descendingIterator();
                int setMax = it.next();
                if (setMax > value ) {
                    it.remove();
                    set.add(value);
                }
            }
        }
        return set;

    }