堆的基本操作與堆排序(C/C++實(shí)現(xiàn))

原理參考:堆和堆排序原理介紹

堆的基本操作(以最小堆為例)

  • 基本數(shù)組的定義

int n; //數(shù)組元素的個(gè)數(shù)
int heap[100005];  //堆數(shù)組
  • 向下調(diào)整操作

向下調(diào)整操作一般是針對(duì)一個(gè)節(jié)點(diǎn)而言的码邻,通過對(duì)其進(jìn)行向下調(diào)整介褥,保證其值是其所在的二叉樹中的最小的數(shù)。

//對(duì)輸入的heap數(shù)組在[low, high]范圍內(nèi)向下調(diào)整窍霞,調(diào)整為最小堆,即小的數(shù)在最上面 
void downHeap(int low, int high){
    //i初始化為欲調(diào)整節(jié)點(diǎn),j為其左孩子 
    int i = low, j = i * 2;
    //如果還存在孩子節(jié)點(diǎn)則一直比較并調(diào)整 
    while(j <= high){
        //如果右孩子存在且值比左孩子還小踢俄,那么就應(yīng)該將右孩子向上調(diào)整 
        if(j + 1 <= high && heap[j+1] < heap[j])
            j = j + 1;
        //當(dāng)孩子的值比欲調(diào)整節(jié)點(diǎn)大時(shí),則進(jìn)行交換晴及,保證小的數(shù)在上面 
        if(heap[j] < heap[i]){
            swap(heap[j], heap[i]);
            //繼續(xù)向下調(diào)整 
            i = j;
            j = j * 2;
        }
        //如果欲調(diào)整節(jié)點(diǎn)的值已經(jīng)比孩子都小則結(jié)束調(diào)整 
        else
            break;
    }
}
  • 建堆操作

建堆操作則利用向下調(diào)整的方法對(duì)堆數(shù)組的每一個(gè)元素進(jìn)行向下調(diào)整都办,從而使得每一個(gè)節(jié)點(diǎn)都是以其為根結(jié)點(diǎn)的樹中的最小元素。而通過二叉樹我們可以知道虑稼,葉子節(jié)點(diǎn)是沒有孩子的琳钉,因此對(duì)于葉子節(jié)點(diǎn)可以不用再進(jìn)行向下調(diào)整,因此第一個(gè)調(diào)整的節(jié)點(diǎn)則是最后一個(gè)元素的父節(jié)點(diǎn)蛛倦,因?yàn)樗菢渲凶詈笠粋€(gè)還擁有孩子的節(jié)點(diǎn)歌懒。實(shí)現(xiàn)代碼如下:

//建堆操作 
void createHeap(){
    //建堆從最后一個(gè)還具有孩子的節(jié)點(diǎn)開始,依次往前遍歷到根結(jié)點(diǎn)溯壶,到最后便建立了最小堆 
    for(int i = n / 2; i >= 1; i--){
        downHeap(i, n);
    }
}
  • 刪除堆頂元素

對(duì)于堆的刪除操作則一般只針對(duì)堆的堆頂元素及皂,而一般不對(duì)其他元素進(jìn)行刪除。對(duì)堆頂元素進(jìn)行刪除時(shí)且改,只需要將堆的最后一個(gè)元素覆蓋堆頂元素然后再進(jìn)行一次向下調(diào)整即可滿足验烧。刪除后的同時(shí)需要將堆的元素個(gè)數(shù)減一。

//刪除堆頂元素 
void deleteHeap(){
    //將堆最后一個(gè)元素覆蓋堆頂元素又跛,再將元素個(gè)數(shù)n簡易 
    heap[1] = heap[n--];
    //從堆頂?shù)蕉炎詈筮M(jìn)行一次向下調(diào)整碍拆,保證刪除后仍是最小堆 
    downHeap(1, n);
} 
  • 向堆插入一個(gè)元素

向堆中插入一個(gè)元素和刪除一個(gè)元素則不同,因?yàn)槌绦蝾A(yù)先是不知道這個(gè)元素應(yīng)該放在什么位置的,而如果直接進(jìn)行查找再添加則會(huì)更加的麻煩感混,體現(xiàn)不出堆的一個(gè)優(yōu)勢(shì)端幼。如果說把它放在第一個(gè)元素位置利用向下調(diào)整,仍會(huì)帶來很多麻煩浩习,比如原本的堆頂元素又放到哪里静暂,如果將原本的堆頂元素放到末尾,那豈不是不是最小堆了谱秽?或者將所有元素往后移動(dòng)洽蛀,將插入的元素放到第一個(gè)位置,再調(diào)整疟赊,這不明顯更加復(fù)雜郊供。
因此在插入一個(gè)元素時(shí),堆中采用向上調(diào)整的方法近哟,而不是向下調(diào)整驮审,首先將插入的元素放入到堆數(shù)組的最后,并將元素個(gè)數(shù)加1吉执,此時(shí)則對(duì)這插入的最后一個(gè)元素進(jìn)行向上調(diào)整疯淫,直到其值比父親節(jié)點(diǎn)大或者到達(dá)了根結(jié)點(diǎn)時(shí)退出。實(shí)現(xiàn)如下:

//對(duì)heap中[low戳玫,high]范圍內(nèi)進(jìn)行向上調(diào)整 
void upHeap(int low, int high){
    //i初始化為欲調(diào)整節(jié)點(diǎn)熙掺,j為其父親節(jié)點(diǎn) 
    int i = high, j = i / 2;
    //當(dāng)未到達(dá)根結(jié)點(diǎn)時(shí)則繼續(xù)操作 
    while(j >= low){
        //如果父親節(jié)點(diǎn)仍然比欲調(diào)整節(jié)點(diǎn)的值大則繼續(xù)向上,因?yàn)樾枰{(diào)整為最小堆 
        if(heap[j] > heap[i]){
            swap(heap[i], heap[j]);
            i = j;
            j = i / 2;
        }
        //比父親節(jié)點(diǎn)值大咕宿,則達(dá)到退出條件 
        else
            break;
    }
}
//插入一個(gè)元素币绩,利用向上調(diào)整方法 
void insert(int x){
    //將新插入的元素放到堆數(shù)組的最后,并將元素個(gè)數(shù)加1 
    heap[++n] = x;
    //放入后進(jìn)行一次向上調(diào)整 
    upHeap(1, n);
}

堆排序(最小堆為例)

有了以上的堆的基本操作之后則可以進(jìn)行堆排序了府阀。對(duì)于堆排序缆镣,此處以最簡單的為例。

首先對(duì)于堆數(shù)組试浙,則是需要用戶進(jìn)行輸入的董瞻,輸入之后,便可以對(duì)每一個(gè)非葉子節(jié)點(diǎn)元素(從最后一個(gè)具有孩子的節(jié)點(diǎn)開始)進(jìn)行一次向下調(diào)整田巴,從而在遍歷之后可以建立一個(gè)最小堆力细。不過這樣之后的數(shù)組并不是有序的,因?yàn)槲覀冎槐WC了當(dāng)前節(jié)點(diǎn)的值比孩子結(jié)點(diǎn)的值小固额,而對(duì)于左右孩子結(jié)點(diǎn)的值是沒有順序的,同理的是左右子樹都不是有序的煞聪,不像二叉搜索樹那樣左子樹的值都是比右子樹要小的斗躏。

因此在建立起最小堆之后,我們已知的是堆頂元素是最小的,因此可以依次將堆頂元素取出來啄糙,排成一個(gè)序列那便是有序的了笛臣。而對(duì)于每次取出的操作可以不必再新開一個(gè)數(shù)組,直接在原堆數(shù)組中進(jìn)行操作隧饼,操作方法為:將堆頂元素和堆末尾的元素交換沈堡,然后針對(duì)前n-1個(gè)元素對(duì)交換后的堆頂元素進(jìn)行一次向下調(diào)整,這樣堆頂便是剩下的n-1個(gè)元素中的最小的元素燕雁,而該元素就是所有元素的第二小的元素诞丽。然后依次這樣操作,第二次則將堆頂元素和n-1位置的元素交換再進(jìn)行一次向下調(diào)整(因?yàn)閚位置已經(jīng)放了最小元素拐格,第二次的n-1個(gè)元素中的最小元素則放在n-1位置)僧免。這樣當(dāng)操作到只剩一個(gè)元素便可結(jié)束操作,得到的堆數(shù)組便是降序排列的捏浊,如果需要升序則逆序輸出即可懂衩。示例代碼如下:

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<stack>


using namespace std;
int n;
int heap[100005]; 


//對(duì)輸入的heap數(shù)組在[low, high]范圍內(nèi)向下調(diào)整,調(diào)整為最小堆金踪,即小的數(shù)在最上面 
void downHeap(int low, int high){
    //i初始化為欲調(diào)整節(jié)點(diǎn)浊洞,j為其左孩子 
    int i = low, j = i * 2;
    //如果還存在孩子節(jié)點(diǎn)則一直比較并調(diào)整 
    while(j <= high){
        //如果右孩子存在且值比左孩子還小,那么就應(yīng)該將右孩子向上調(diào)整 
        if(j + 1 <= high && heap[j+1] < heap[j])
            j = j + 1;
        //當(dāng)孩子的值比欲調(diào)整節(jié)點(diǎn)大時(shí)胡岔,則進(jìn)行交換法希,保證小的數(shù)在上面 
        if(heap[j] < heap[i]){
            swap(heap[j], heap[i]);
            //繼續(xù)向下調(diào)整 
            i = j;
            j = j * 2;
        }
        //如果欲調(diào)整節(jié)點(diǎn)的值已經(jīng)比孩子都小則結(jié)束調(diào)整 
        else
            break;
    }
}
//建堆操作 
void createHeap(){
    //建堆從最后一個(gè)還具有孩子的節(jié)點(diǎn)開始,依次往前遍歷到根結(jié)點(diǎn)姐军,到最后便建立了最小堆 
    for(int i = n / 2; i >= 1; i--){
        downHeap(i, n);
    }
}
//堆排序操作 
void heapSort(){
    //首先建立初始最小堆 
    createHeap();
    //從后遍歷每一個(gè)元素铁材,每次將最小元素放入到i位置,這樣第一次最小放到n
    //第二次的最小則放入到n-1奕锌,第三次則會(huì)放入到n-2...... 
    for(int i = n; i > 1; i--){
        swap(heap[i], heap[1]);
        downHeap(1, i-1);
    }
}

//10
//2 8 4 6 1 10 7 3 5 9
int main() {
    while(scanf("%d", &n) != EOF) {
        for(int i = 1; i <= n; i++)
            scanf("%d", &heap[i]);
        heapSort();
        for(int i = n; i >= 1; i--)
            printf("%d ", heap[i]);
    }
    return 0;
}

練習(xí)題目:堆排序

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末著觉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子惊暴,更是在濱河造成了極大的恐慌饼丘,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辽话,死亡現(xiàn)場離奇詭異肄鸽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)油啤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門典徘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人益咬,你說我怎么就攤上這事逮诲。” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵梅鹦,是天一觀的道長裆甩。 經(jīng)常有香客問我,道長齐唆,這世上最難降的妖魔是什么嗤栓? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮箍邮,結(jié)果婚禮上茉帅,老公的妹妹穿的比我還像新娘。我一直安慰自己媒殉,他們只是感情好担敌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著廷蓉,像睡著了一般全封。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上桃犬,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天刹悴,我揣著相機(jī)與錄音,去河邊找鬼攒暇。 笑死土匀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的形用。 我是一名探鬼主播就轧,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼田度!你這毒婦竟也來了妒御?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤镇饺,失蹤者是張志新(化名)和其女友劉穎乎莉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奸笤,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惋啃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了监右。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片边灭。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖健盒,靈堂內(nèi)的尸體忽然破棺而出绒瘦,到底是詐尸還是另有隱情宠互,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布椭坚,位于F島的核電站,受9級(jí)特大地震影響搏色,放射性物質(zhì)發(fā)生泄漏善茎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一频轿、第九天 我趴在偏房一處隱蔽的房頂上張望垂涯。 院中可真熱鬧,春花似錦航邢、人聲如沸耕赘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽操骡。三九已至,卻和暖如春赚窃,著一層夾襖步出監(jiān)牢的瞬間册招,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工勒极, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留是掰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓辱匿,卻偏偏與公主長得像键痛,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子匾七,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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