1. 程式人生 > >最小堆解決TopK 問題

最小堆解決TopK 問題

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; } }