簡介
堆是一種基于完全二叉樹的數(shù)據(jù)結(jié)構(gòu).
完全二叉樹:
- 每個節(jié)點(diǎn)最多有兩個子節(jié)點(diǎn)(二叉)
- 除了最底層, 其他每一層都必須填滿, 最底層也需要從左到右依次填入數(shù)據(jù).
當(dāng)一棵完全二叉樹滿足下列條件時即稱為堆:
每個父節(jié)點(diǎn)都大于等于(或者小于等于)它的兩個子節(jié)點(diǎn).
大于等于的情況稱為大根堆, 小于等于的情況稱為小根堆.
本文以小根堆為例, 大根堆可以很容易類比.
如下圖所示即為一個小根堆:
堆的存儲
堆的存儲通過數(shù)組來實現(xiàn), 由于其滿足完全二叉樹的性質(zhì).
則有第i個節(jié)點(diǎn)(i從0開始算)的
父節(jié)點(diǎn): (i-1)/2 // 為負(fù)數(shù)時則說明父節(jié)點(diǎn)不存在
左右子節(jié)點(diǎn): (i*2+1) 和 (i*2+2)
插入堆
給出一個數(shù)組存儲的堆, 如果加入了新元素, 必須想辦法保持堆的特性:
完全二叉 和 父節(jié)點(diǎn)小于等于其子節(jié)點(diǎn)
加入新元素后, 只需要不斷與其父節(jié)點(diǎn)進(jìn)行比較, 根據(jù)大小關(guān)系進(jìn)行調(diào)整.
即分為兩步:
1.將新的數(shù)放入數(shù)組尾部.
2.將最后一個數(shù)向上調(diào)整.
C++實現(xiàn)(代碼可直接運(yùn)行查看結(jié)果)
#include<iostream>
#include<vector>
using namespace std;
void fix_up(vector <int>& vec)
{
int pos = vec.size() - 1;
int n_val = vec[pos];
int parent = (pos - 1) / 2;
while (parent >= 0 && n_val < vec[parent]){ // 有父母 且 值小于父母
swap(vec[parent], vec[pos]);
pos = parent;
parent = (pos - 1) / 2;
n_val = vec[pos];
}
}
void heap_insert(vector <int>& vec, int val)
{
vec.push_back(val); // 1.放到尾部
fix_up(vec); // 2.向上調(diào)整
}
int main()
{
vector <int> vec = { 1,4,8,7,30,10,15 };
heap_insert(vec, 3);
for (auto it : vec) {
cout << it << " ";
}
return 0;
}
從堆中刪除
堆結(jié)構(gòu)僅支持從堆頂進(jìn)行POP操作, 每次都能夠POP出最小的元素.
POP以后堆結(jié)構(gòu)即遭到破壞(缺失了首元素), 此時可以通過下列步驟恢復(fù):
- 將最后一個元素放到堆頂.
- 將堆頂元素向下調(diào)整.
C++實現(xiàn)(代碼可直接運(yùn)行查看結(jié)果)
#include<iostream>
#include<vector>
using namespace std;
void fix_down(vector <int>& vec){
if (vec.empty())
return;
int pos = 0;
int n_val = vec[pos];
int left = pos * 2 + 1;
int right = left + 1;
while (left < vec.size()){
int *ref;
int npos;
if (right < vec.size()) {
ref = &(vec[left] < vec[right] ? vec[left] : vec[right]) ; // 跟子節(jié)點(diǎn)中較小節(jié)點(diǎn)比較.
npos = vec[left] < vec[right] ? left : right; //下一步的位置
}
else {
ref = &vec[left];
npos = left;
}
if (n_val > *ref) {
swap(vec[pos], *ref);
}
else
break;
pos = npos;
left = pos * 2 + 1;
right = left + 1;
if(pos < vec.size())
n_val = vec[pos];
}
}
void pop(vector <int>& vec)
{
cout << "pop " << vec[0] << endl;
vec[0] = vec[vec.size() - 1]; // 1. 把尾部的值放到頭部
vec.pop_back();
fix_down(vec); // 2. 向下調(diào)整
}
int main()
{
vector <int> vec = { 1,4,8,7,30,10,15 };
while (!vec.empty()){
pop(vec);
}
return 0;
}
數(shù)組堆化
這一part要解決給出一個數(shù)組, 用這個數(shù)組構(gòu)建堆的問題.
解決了堆的插入, 刪除等操作的問題, 再解決堆化的問題就比較容易了.
有以下兩種思路:
- 把數(shù)組里的數(shù)一個一個取出來, 插入堆中.
- 對數(shù)組里的每一個非葉子節(jié)點(diǎn)的數(shù)進(jìn)行向下調(diào)整的操作.
以上兩種思路均可以通過上述實現(xiàn)的調(diào)整函數(shù)進(jìn)行實現(xiàn).
注:思路2下, 最后一個非葉子節(jié)點(diǎn)的位置為n/2-1, 所以從n/2-1往回遍歷即可.
堆排序
由于堆的頂部總是最小的數(shù), 只需要每次將頂部的數(shù)取出, 然后再將堆調(diào)整為最小堆即可.
取出一個數(shù), 最多需要調(diào)整logN次, 有N個數(shù)需要取出
所以堆排序的時間復(fù)雜度為NlogN.