在看libevent時看到它用小根堆來管理時間,忽然猛地發現堆的概念有點忘了(上一次看數據結構是兩年半之前的一個暑假),所以此處複習一下。
堆是利用完全二叉樹來維護的一種數據結構,存取操作時間複雜度在O(1)-O(log n)之間,完全二叉樹就是除了最後一層外,其他所有層都是滿節點,且最後一層如果缺少節點,缺的部分應該在最右邊。
堆分爲兩種類型,大根堆與小根堆,以大根堆爲例,每個節點都小於它的父節點。假設現在隨手寫一串數字 1 , 2 , 10 , -1, 0 ,8,3
先建立成完全二叉樹
要把它初始化爲一個大頂堆的話,只需要從最後一個非葉子節點開始,依次做下沉動作。對了,操作可分爲上浮跟下沉,就是向上或向下調整到對應的位置。最後一個葉子節點索引號可用總個數/2 得到。因爲只要所有的非葉子節點位置正確,那整個堆就是正確的。此處就是10這個節點,下沉動作就是向葉子節點找一個大一點的進行比較,如果大於自己,則進行對調,對調後重複比較動作。所以10就是跟8,3比較,沒有比10 大的,不做任何改變。同樣2,也是。 到1這個節點,首先由於2<10,並且1<10所以,1和10對調
接着,由於3<8,且1<8,所以1要跟8對調。最終結果就是下面這張圖
可以看到,10大於2和8, 2大於-1和0 , 8大於1和3 滿足大根堆的性質。
上浮動作類似,就是一直跟父節點比較,如果自己大於父節點,那就對調位置,對調後繼續跟新的父節點比較,重複這個比較動作即可。
很多書上講堆排序,其實就是做一個大根堆,然後每次取出根節點,然後把最後一個節點放到第一個,再做下沉動作。這樣保證取出一個節點後仍然爲一個大根堆,接着繼續取,繼續調整...最終節點個數爲1,結束。
下面給出一個代碼,其中通過>>運算符可以插入一個節點,<<運算符可以每次取根元素並調整,也就是說cout這個對象後會得到一個有序序列。
#include<iostream>
using namespace std;
class Big_Heap
{
public:
void shift_down(int k) //大根堆
{
for (int i = 2 * k; i <= size; i *= 2) //i*2 下一次循環時就會指向當前的左孩子節點
{
if (i < size && queue[i] < queue[i + 1]) //如果右孩子較大則讓i指向右孩子
i++;
if (queue[k] < queue[i])
{
std::swap(queue[i], queue[k]);
k = i;
}
else
break; // 調整結束
}
}
void shift_up(int k)
{
for (int i = k/2; i >= 1; i/=2)
{
if (queue[i] < queue[k])
{
std::swap(queue[i], queue[k]);
k = i;
}
}
}
friend ostream & operator << (ostream &, Big_Heap &);
Big_Heap& operator >> (int t)
{
queue[++size] = t;
shift_up(size);
for (int i = 1; i <= size; i++)
cout << queue[i] << ' ';
cout << endl;
return *this;
}
void pop() //彈出操作
{
int temp = queue[1];
queue[1] = queue[size];
queue[size] = temp;
size--;
shift_down(1);
}
int top() { return queue[1]; }
private:
int size = 0;
int queue[1024];
};
ostream & operator << (ostream & out, Big_Heap & obj)
{
const int num = obj.size;//下面不斷在pop,會改變size的值,所以要先保存下
for (int i = 1; i <= num; i++)
{
out << obj.top() << " "; //輸出堆頂元素
obj.pop(); //彈出堆頂元素
}
return out;
}
int main()
{
Big_Heap obj;
obj >> 1 >> 2 >> 10 >> -1 >> 0 >> 8 >> 3;
cout << obj;
system("pause");
}
這是大根堆的實現方式,小根堆就只需要改幾個符號即可,不再重複貼出代碼