最小堆解決TopK 問題
阿新 • • 發佈:2019-02-05
TopK問題是指從大量資料(源資料)中獲取最大(或最小)的K個數據。
解決方法一、
對源資料中所有資料進行排序,取出前K個數據,就是TopK。
解決方法二
維護一個K長度的陣列a[],先讀取源資料中的前K個放入陣列,對該陣列進行升序排序,再依次讀取源資料第K個以後的資料,和陣列中最小的元素(a[0])比較,如果小於a[0]直接pass,大於的話,就丟棄最小的元素a[0],利用二分法找到其位置,然後該位置前的陣列元素整體向前移位,直到源資料讀取結束。時間複雜度是O(nlogK),但是當K的值較大的時候,長度為K的資料整體移位,也是非常耗時的。
解決方法三
利用最小堆,時間複雜度是O(nlogK)
最小堆(小根堆)是一種資料結構,它首先是一顆完全二叉樹,並且,它所有父節點的值小於或等於兩個子節點的值。如下圖:
堆有幾個重要操作:
BuildHeap:將普通陣列轉換成堆,轉換完成後,陣列就符合堆的特性:所有父節點的值小於或等於兩個子節點的值。
Heapify(int i):當元素i的左右子樹都是小根堆時,通過Heapify讓i元素下降到適當的位置,以符合堆的性質。
程式碼如下:程式碼中包含註釋
public class MinHeap {
// 堆的儲存結構 - 陣列
private int[] data;
//將一個數組傳入建構函式, 並轉換成一個最小堆
public MinHeap(int[] data) {
this.data = data;
buildHeap();
}
/**
* 完全二叉樹只有陣列下標小於或等於 (data.length) / 2 - 1 的元素有孩子結點,遍歷這些結點。
* 比如上面的圖中,陣列有10個元素, (data.length) / 2 - 1的值為4,a[4]有孩子結點,但a[5]沒有
*/
private void buildHeap() {
for (int i = (data.length / 2) - 1 ; i >= 0; i--) {
heapify(i);
}
}
private void heapify(int i) {
int right = (i + 1) << 1; //獲取右節點陣列下標
int left = ((i + 1) << 1) - 1; //獲取左節點陣列下標
int smallest = i;
// 存在左結點,且左結點的值小於根結點的值
if (left < data.length && data[left] < data[i]) {
smallest = left;
}
// 存在右結點,且右結點的值小於以上比較的較小值
if (right < data.length && data[right] < data[smallest]) {
smallest = right;
}
if (i == smallest) {
return;
}
// 交換根節點和左右結點中最小的那個值,把根節點的值替換下去
int tmp = data[i];
data[i] = data[smallest];
data[smallest] = tmp;
// 由於替換後左右子樹會被影響,所以要對受影響的子樹再進行heapify
heapify(smallest);
}
/**
* 獲取堆中最小的元素, 根元素
*/
private int getRoot() {
if (data.length != 0) {
return data[0];
}
return -1;
}
/**
* 替換根元素並重新heapify
*/
private void setRoot(int root) {
data[0] = root;
heapify(0);
}
public static void main(String[] args) {
int[] data = new int[]{12, 23, 4, 2, 3, 32, 42, 1, 33, 55, 2, 88, 18, 5, 12};
int[] topK = topK(data, 8);
for (int tmp : topK) {
System.out.println(tmp);
}
}
// 當資料大於堆中最小的數(根節點)時,替換堆中的根節點,再轉換成堆
private static int[] topK(int[] data, int k) {
int[] topK = new int[k];
//取前K個元素放到tok 陣列中
System.arraycopy(data, 0, topK, 0, k);
MinHeap heap = new MinHeap(topK);
for (int i = k; i < data.length; i++) {
int root = heap.getRoot();
if (data[i] > root) {
heap.setRoot(data[i]);
}
}
return topK;
}
}