[數(shù)據(jù)結(jié)構(gòu)]堆原理及其C++實現(xiàn)

簡介

堆是一種基于完全二叉樹的數(shù)據(jù)結(jié)構(gòu).
完全二叉樹:

  1. 每個節(jié)點(diǎn)最多有兩個子節(jié)點(diǎn)(二叉)
  2. 除了最底層, 其他每一層都必須填滿, 最底層也需要從左到右依次填入數(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ù)組存儲

插入堆

給出一個數(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)整.

調(diào)整
調(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ù):

  1. 將最后一個元素放到堆頂.
  2. 將堆頂元素向下調(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)建堆的問題.
解決了堆的插入, 刪除等操作的問題, 再解決堆化的問題就比較容易了.
有以下兩種思路:

  1. 把數(shù)組里的數(shù)一個一個取出來, 插入堆中.
  2. 對數(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.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末更舞,一起剝皮案震驚了整個濱河市忠聚,隨后出現(xiàn)的幾起案子蹬刷,更是在濱河造成了極大的恐慌冀续,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扮惦,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)袋狞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來映屋,“玉大人硕并,你說我怎么就攤上這事⊙砭#” “怎么了倔毙?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乙濒。 經(jīng)常有香客問我陕赃,道長卵蛉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任么库,我火速辦了婚禮傻丝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘诉儒。我一直安慰自己葡缰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布忱反。 她就那樣靜靜地躺著泛释,像睡著了一般。 火紅的嫁衣襯著肌膚如雪温算。 梳的紋絲不亂的頭發(fā)上怜校,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天,我揣著相機(jī)與錄音注竿,去河邊找鬼茄茁。 笑死蘑斧,一個胖子當(dāng)著我的面吹牛巴刻,可吹牛的內(nèi)容都是我干的戴陡。 我是一名探鬼主播县袱,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼职辅,長吁一口氣:“原來是場噩夢啊……” “哼姓迅!你這毒婦竟也來了敬锐?” 一聲冷哼從身側(cè)響起闰非,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤蒲祈,失蹤者是張志新(化名)和其女友劉穎甘萧,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梆掸,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扬卷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了酸钦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怪得。...
    茶點(diǎn)故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖卑硫,靈堂內(nèi)的尸體忽然破棺而出徒恋,到底是詐尸還是另有隱情,我是刑警寧澤欢伏,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布入挣,位于F島的核電站,受9級特大地震影響硝拧,放射性物質(zhì)發(fā)生泄漏径筏。R本人自食惡果不足惜葛假,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望滋恬。 院中可真熱鬧聊训,春花似錦、人聲如沸恢氯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勋拟。三九已至勋磕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間指黎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工州丹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留醋安,地道東北人。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓墓毒,卻偏偏與公主長得像吓揪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子所计,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容