top k演算法講解
阿新 • • 發佈:2019-01-09
在實際工作中,我們時常會有尋找長度為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;
}
,