在數據結構中,堆其實就是一棵徹底二叉樹。咱們知道內存中也有一塊叫作堆的存儲區域,可是這與數據結構中的堆是徹底不一樣的概念。在數據結構中,堆分爲大根堆和小根堆,大根堆就是根結點的關鍵字大於等於任一個子節點的關鍵字,而它的左右子樹又分別都是大根堆;小根堆與大根堆剛好相反。在C++的STL中優先隊列priority_queue結構就是實現的堆結構。下來本身動手現實一個堆結構,包括heap_init,heap_insert,heap_top等操做。git
一、堆的實現github
由於堆是一棵徹底二叉樹,因此咱們能夠用順序表來實現,並且堆也只能用順序表。咱們用vector。算法
(1) 堆的初始化數組
對堆的初始化基本思想:首先初始數組是一個雜亂無章的序列,可是若是堆中只有一個元素heap[0],那麼heap[0]自己是一個堆,而後加入heap[1]調整堆;繼續加入heap[2].....直到完成全部元素的調整。數據結構
void sift_up(vector<int> &heap,int index){ while((index+1)/2-1 >= 0){ if(heap[(index+1)/2-1] < heap[index]){ swap(&heap[(index+1)/2-1],&heap[index]); index = (index+1)/2-1; }else break; } } void heap_init(vector<int> &heap){ if(heap.empty()) return ; for(int i=1; i<heap.size(); i++){ sift_up(heap,i); } }
(2) 向堆中插入元素性能
把插入的元素放入堆的末尾,而後向上調整堆。spa
void heap_insert(vector<int> &heap,int element){ heap.push_back(element); sift_up(heap,heap.size()-1); }
(3) 取出堆頂的元素code
取出一個元素後,用最後一個元素填補第一個元素的位置,而後向下依次調整堆。blog
void sift_down(vector<int> &heap,int index){ while(index*2+2 < heap.size()){ if(heap[index*2+1]>=heap[index*2+2] && heap[index]<heap[index*2+1]){ swap(&heap[index],&heap[index*2+1]); index = index*2+1; }else if(heap[index*2+1]<heap[index*2+2] && heap[index]<heap[index*2+2]){ swap(&heap[index],&heap[index*2+2]); index = index*2+2; }else break; } } bool heap_top(vector<int> &heap,int *res){ if(heap.empty()) return false; *res = heap[0]; heap[0] = heap[heap.size()-1]; heap.erase(heap.end()-1); sift_down(heap,0); return true; }
二、堆排序排序
首先初始化堆,而後依次取出堆頂的值。這裏爲大根堆,因此是從大到小排序。
void heap_sort(vector<int> &vec){ heap_init(vec); int len = vec.size(); while(len--){ int num; heap_top(vec,&num); printf("%d ",num); } }
堆排序的時間複雜度爲O(nlog2n),從上面排序的步驟能夠看出它是不穩定的排序。可是它與選擇排序,歸併排序同樣時間複雜度不隨序列的分佈變化而變化。而對於插入排序和冒泡排序來講,當輸入序列有序或者基本有序時,它們的複雜度會遞減爲O(n),而快速排序則會退化成O(n2)。
因此在具體應用中,要根據輸入序列來選擇哪一種排序方法,具體問題具體分析。因爲堆排序特殊的排序結構和優良的性能,因此在不少時候下均可以採用堆排序。
三、堆排序的應用
在一個n個數的序列中取其中最大的k個數(Top k問題)。
這是一個很常見的排序算法題。
方法一:直接對這這n個數進行排序,而後取k個數。時間複雜度最少爲O(nlog2n)。
方法二:借鑑快排的思路,並不須要完整地實現快排,只須要實現快排的一部分便可獲得最大的k個數。複雜度爲O(nlog2k)。
方法三:能夠採用哈希排序,先把n中開始的k個數放入hash表中,而後依次從剩下的的n-k個數中取出一個,與hash表中的k個數比較,每次淘汰最小的那個數。時間複雜度爲O((n-k)*k)。
方法四:取出n中開始的k個數,創建一個小根堆,而後從剩下的n-k個數中,每次取出一個數插入小根堆中,而後刪除堆頂的那個元素(堆中的最小值)。時間複雜度爲O(*(n-k)*lg2k)。
不能否認,採用堆來求最大的k個數性能是最好的,可是好處還不止這麼一點點!!咱們試想一下,若是輸入的序列很大,也就是n值很大,以至於沒法所有存放在內存中,那麼這時候,方法一和方法二就無論用了,固然方法一採用歸併排序能夠達到目的,可是這時候須要多少次IO??。若是選擇方法四,最多隻須要(n-k)次IO,固然方法三也是如此,只是每次須要比較k次。
完整代碼詳見:https://github.com/whc2uestc/DataStructure-Algorithm/tree/master/heap
版權全部,歡迎轉載,轉載請註明出處。