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

簡(jiǎn)介

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

  1. 每個(gè)節(jié)點(diǎn)最多有兩個(gè)子節(jié)點(diǎn)(二叉)
  2. 除了最底層, 其他每一層都必須填滿, 最底層也需要從左到右依次填入數(shù)據(jù).

當(dāng)一棵完全二叉樹(shù)滿足下列條件時(shí)即稱為堆:

每個(gè)父節(jié)點(diǎn)都大于等于(或者小于等于)它的兩個(gè)子節(jié)點(diǎn).

大于等于的情況稱為大根堆, 小于等于的情況稱為小根堆.
本文以小根堆為例, 大根堆可以很容易類比.
如下圖所示即為一個(gè)小根堆:

小根堆

堆的存儲(chǔ)

堆的存儲(chǔ)通過(guò)數(shù)組來(lái)實(shí)現(xiàn), 由于其滿足完全二叉樹(shù)的性質(zhì).
則有第i個(gè)節(jié)點(diǎn)(i從0開(kāi)始算)
父節(jié)點(diǎn): (i-1)/2 // 為負(fù)數(shù)時(shí)則說(shuō)明父節(jié)點(diǎn)不存在
左右子節(jié)點(diǎn): (i*2+1) 和 (i*2+2)

數(shù)組存儲(chǔ)

插入堆

給出一個(gè)數(shù)組存儲(chǔ)的堆, 如果加入了新元素, 必須想辦法保持堆的特性:
完全二叉父節(jié)點(diǎn)小于等于其子節(jié)點(diǎn)

加入新元素后, 只需要不斷與其父節(jié)點(diǎn)進(jìn)行比較, 根據(jù)大小關(guān)系進(jìn)行調(diào)整.
即分為兩步:

1.將新的數(shù)放入數(shù)組尾部.
2.將最后一個(gè)數(shù)向上調(diào)整.

調(diào)整
調(diào)整后

C++實(shí)現(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)即遭到破壞(缺失了首元素), 此時(shí)可以通過(guò)下列步驟恢復(fù):

  1. 將最后一個(gè)元素放到堆頂.
  2. 將堆頂元素向下調(diào)整.

C++實(shí)現(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要解決給出一個(gè)數(shù)組, 用這個(gè)數(shù)組構(gòu)建堆的問(wèn)題.
解決了堆的插入, 刪除等操作的問(wèn)題, 再解決堆化的問(wèn)題就比較容易了.
有以下兩種思路:

  1. 把數(shù)組里的數(shù)一個(gè)一個(gè)取出來(lái), 插入堆中.
  2. 對(duì)數(shù)組里的每一個(gè)非葉子節(jié)點(diǎn)的數(shù)進(jìn)行向下調(diào)整的操作.

以上兩種思路均可以通過(guò)上述實(shí)現(xiàn)的調(diào)整函數(shù)進(jìn)行實(shí)現(xiàn).
注:思路2下, 最后一個(gè)非葉子節(jié)點(diǎn)的位置為n/2-1, 所以從n/2-1往回遍歷即可.

堆排序

由于堆的頂部總是最小的數(shù), 只需要每次將頂部的數(shù)取出, 然后再將堆調(diào)整為最小堆即可.
取出一個(gè)數(shù), 最多需要調(diào)整logN次, 有N個(gè)數(shù)需要取出
所以堆排序的時(shí)間復(fù)雜度為NlogN.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評(píng)論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,423評(píng)論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,991評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,761評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,207評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,419評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,653評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,901評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,678評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,978評(píng)論 2 374

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