在處理大量資料的時候, 有時候往往需要找出Top前幾的資料, 這時候如果直接對資料進行排序, 在處理海量資料的時候往往就是不可行的了, 而且在排序最好的時間複雜度為nlogn, 當n遠大於需要獲取到的資料的時候, 時間複雜度就顯得過高。 使用最小堆或者最大堆可以很好地解決Top大問題或者Top小問題。
Top大問題解決思路:使用一個固定大小的最小堆, 當堆滿後, 每次添加資料的時候與堆頂元素比較, 若小於堆頂元素, 則捨棄, 若大於堆頂元素, 則刪除堆頂元素, 添加新增元素, 對堆進行重新排序。 Top小問題解決思路:使用一個固定大小的最大堆,對於n個數, 取Top m個數, 時間複雜度為O(nlogm), 這樣在n較大情況下, 是優於nlogn的時間複雜度的。
比如10000個資料, 取前100大的數, 那麼時間複雜度就是O(10000log100)。 因為在插入資料的時候需要遍歷元素時間複雜度達到了O(10000), 然後每次插入過程中進行調整的複雜度為O(log100), 所以總體時間複雜度為O(10000log100)。
使用Java類庫集合實現
Java集合中的PriorityQueue就可以實現最大堆或者最小堆, 從名字可以知道該集合是優先佇列, 資料結構中的優先佇列就是使用堆來實現的。
// 底層通過一個Object類型資料保存元素 transient Object queue; // 通過Comparator制定比較方法 private final Comparator comparator; // 其中一個構造函數 public PriorityQueue(int initialCapacity, Comparator comparator) { // Note: This restriction of at least one is not actually needed, // but continues for 1.5 compatibility if (initialCapacity下面就使用PriorityQueue來實現最小堆和最大堆。
運行結果:
使用Java實現
通過上述講述, 基本瞭解最大堆和最小堆情況以及它們與TopK問題的關係, 上面是使用集合實現, 下面使用Java來實現最小堆, 並解決TopK大問題。
限定數據大小。 若堆滿, 則插入過程中與堆頂元素比較, 並做相應操作。 每次刪除堆頂元素後堆做一次調整, 保證最小堆特性。 public class TopK { int items; int currentSize = 0; // 初始化為size + 1, 從下標1開始保存元素。 public TopK(int size) { items = new int[size + 1]; } // 插入元素 public void insert(int x) { if (currentSize == items.length - 1) { if (compare(x, items[1]) 0) { deleteMin; } } int hole = ++currentSize; for (items[0] = x; compare(x, items[hole / 2]) b) { return 1; } return 0; } public static void main(String[] args) { TopK topK = new TopK(10); for (int i = 1; i