堆是數據結構的一種,可用於排序和海量數據處理中的TopK問題
堆的邏輯結構:是一顆完全二叉樹,由於是是一顆完全二叉樹我們就可通過數組來實現他的存儲方式。
上面是一顆完全二叉樹,分別爲樹狀存儲、數組存儲。
堆的性質:
1.堆分爲大堆和小堆。
2.大堆的對頂大於它的左子樹和右子樹(小堆相反)。
3.左右子樹分別爲大堆(或者小堆)。
4.爲方便計數滿足數組的存儲方式有以下規律:
a.lchild(左孩子下標) = parent2+1;
b.rchild(右孩子下標) = parent2+2;
c.parent(父親節點下標) = (child-1)/2;
有了上述性質對堆在數組中表示和操作就方便多了
對堆有一下操作:
1.建堆(利用向下調整)
2.刪除(利用向下調整)
3.插入(利用向上調整)
在講建堆之前先說明一下向下調整算法(以大堆爲實例)
建堆:通過了解到向下調整算法建堆的步驟就很簡單了(從第一個非葉子節點開始調用向下調整算法)
刪除:實際就是刪除堆頂元素,只需要交換堆頂和最後一個元素(即數組末尾)然後刪除最後一個元素。對堆頂進行一次向下調整即可。
插入:實際上是在堆尾插入一個元素而非任意位置。插入只會影響一條路徑上的元素,故只需調用向上調整算法即可。
向上調整算法:
通過上面分析,整體思路已經有了,下面是實現的代碼(由於爲了說明TopK問題和堆排問題,用到堆,所以將一些接口放開以便TopK和堆排調用):
#include<iostream> using namespace std; #define MAX 100 //仿函數 template<class T> class Greate{ public: bool operator()(const T& L, const T& R) { return L > R; } }; template<class T> class Little{ public: bool operator()(const T& L, const T& R) { return L < R; } }; template<class T,class com> class Heap { public: Heap(int size) :_size(size) ,heap(new T[MAX]) { } void CreatHeap(int* array,int size) { for (int i = 0; i <size; i++) { heap[i] = array[i]; } //建堆 for (int i = (_size - 2) / 2; i >= 0; i--) { AdjustDown(heap, i); } } void InsertHeap(int num) { heap[_size] = num; _size += 1; AdjustUp(heap, _size-1); } bool EarseHeap() { if (_size) { swap(heap[0], heap[_size - 1]); _size--; AdjustDown(heap, 0); return true; } return false; } //向下調整 void AdjustDown(int* h, int root) { int parent = root; int lchild = parent * 2 + 1; while (lchild < _size) { if (lchild + 1 < _size && com(h[lchild+1] , h[lchild])) lchild++; if (com(h[lchild], h[parent])) { swap(h[parent], h[lchild]); parent = lchild; lchild = parent * 2 + 1; } else break; } } //向上調整 void AdjustUp(int* h, int child) { int parent = (child - 1) / 2; while (parent >= 0) { if (com(h[child], h[parent])) { swap(h[child], h[parent]); child = parent; parent = (child - 1) / 2; } else break; } } void print() { for (int i = 0; i < _size; i++) { cout << heap[i] << " "; } cout << endl; } public: T _size; T* heap; com com; };
爲了實現大小堆,這裏用到了仿函數,可以通過模板來選擇構建大堆或小堆。
上述堆已經實現了,下面我們藉助堆來解決一些實際問題:TopK、堆排
1.TopK問題描述:有百萬數據需要我們處理,即找到這百萬數據中的前K個最大的或者最小的。
在此之前我將一個現實生活的實例:比如我們想進入全國學霸的前二十個,我們需要做神魔,其實我們的分數只需要比前二十個中成績最低的同學高就可以進入學霸排行榜了。現在我們可以類似的套用這種模式解決我們的TopK問題了吧!
步驟(以前k個最大的爲例):1.建一個大小爲k小堆(百萬箇中1~k來建立)2.從第k+1個數據開始和堆頂比較比堆頂大,入堆,然後從根開始向下調整,最後堆裏面剩下的就是最大的k個。(介於方法一樣,這裏就不講前k個最小的了)。
代碼實現:
void TopK(int array[],int size,int K) { Heap<int, Little<int>> h1(3); h1.CreatHeap(array, 3); for (int i = 3; i < size; i++) { if (array[i]>h1.heap[0]) h1.heap[0] = array[i]; h1.AdjustDown(h1.heap, 0); } h1.print(); } void testTopK() { int array[] = { 1000, 99, 78, 53, 17, 78, 9, 45, 65, 87, 23, 31 }; int k = 3; TopK(array, sizeof(array) / sizeof(array[0]), k); }
說明:爲了直觀看到效果,我在堆裏實現了打印的接口
堆排:利用堆排序的算法。
還是畫圖比較好理解:
代碼實現:
void HeapSort(int array[],int size) { Heap<int, Greate<int>> Gheap(size); Gheap.CreatHeap(array, size); //堆排序 while (Gheap._size-1) { swap(Gheap.heap[0], Gheap.heap[Gheap._size - 1]); Gheap._size--; Gheap.AdjustDown(Gheap.heap,0); } Gheap._size = size; Gheap.print(); } void test_HeapSort() { int array[] = { 1000, 99, 78, 53, 17, 78, 9, 45, 65, 87, 23, 31 }; int size = sizeof(array) / sizeof(array[0]); HeapSort(array, size); }