數(shù)據(jù)結(jié)構(gòu)之二叉堆

二叉堆

二叉堆是一棵完全二叉樹胡野,什么是完全二叉樹呢?簡(jiǎn)單來(lái)說(shuō)硫豆,就是按照層的順序笼呆,對(duì)樹的節(jié)點(diǎn)標(biāo)號(hào),然后按照層次遍歷的順序來(lái)遍歷诗赌,得到的結(jié)果是按照順序來(lái)標(biāo)號(hào)的汗茄,不能出現(xiàn)斷點(diǎn)铭若,這就是一個(gè)完全二叉樹。這么說(shuō)比較抽象瞳腌,舉個(gè)例子來(lái)說(shuō)镜雨。

1.png

這個(gè)二叉樹的層次遍歷結(jié)果是0,1荚坞,2挑宠,3西剥,4亿汞,5,6,7南捂,8.所以是一個(gè)完全二叉樹旧找。

2.png

這里這棵二叉樹進(jìn)行層次遍歷溺健,得到就是0钮蛛,1,2岭辣,3,4沦童,5叹话,6偷遗,7驼壶,10.編號(hào)不是連續(xù)的,就不是一棵完全二叉樹箩溃。

堆就是一棵完全二叉樹碌嘀,不過(guò)因?yàn)橥耆鏄涞奶匦曰林迹趯?shí)現(xiàn)它的時(shí)候往往不是使用鏈表股冗,而是使用數(shù)組來(lái)實(shí)現(xiàn),我們可以把上面的二叉樹的節(jié)點(diǎn)標(biāo)號(hào)與數(shù)組的下標(biāo)對(duì)應(yīng)起來(lái)止状。二叉樹的根節(jié)點(diǎn)標(biāo)號(hào)從1開始,存儲(chǔ)的數(shù)組也從下標(biāo)1開始存儲(chǔ)二叉樹怯疤,一一對(duì)應(yīng),這種情況下伏社,父節(jié)點(diǎn)的數(shù)組下標(biāo)為i,則左子樹的數(shù)組下標(biāo)為2i摘昌,右子樹的下標(biāo)為2i+1(如果沒(méi)有超出數(shù)組的界限的情況下)

3.png

上圖的二叉樹將被存儲(chǔ)為下面的數(shù)組

4.png

在程序里就是如下的訪問(wèn)子節(jié)點(diǎn)和父節(jié)點(diǎn)。

int heap[10];
//訪問(wèn)第三個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn)
heap[3/2];
//訪問(wèn)第三個(gè)節(jié)點(diǎn)的左子節(jié)點(diǎn)
heap[3*2]
//訪問(wèn)第三個(gè)節(jié)點(diǎn)的右子節(jié)點(diǎn)
heap[3*2+1]

如果是最大堆罕容,那么父節(jié)點(diǎn)都要比兩個(gè)子節(jié)點(diǎn)的數(shù)值大稿饰,相應(yīng)的最小堆就是父節(jié)點(diǎn)比兩個(gè)子節(jié)點(diǎn)都要小。而哨兵的作用就是充當(dāng)邊界檢測(cè)的角色喉镰。如果是最大堆,那么這個(gè)值比堆中的數(shù)據(jù)都要大梧喷,如果是最小堆,那么這個(gè)值比堆中所有數(shù)據(jù)都要小汇歹。

堆的程序?qū)崿F(xiàn)
堆的結(jié)構(gòu)定義
typedef int Element;
typedef struct HeapNode
{
    int heapsize; //堆的最大尺寸偿凭,也就是數(shù)組的最大值
    int size;//堆的當(dāng)前大小
    Element *data;//數(shù)組指針产弹,動(dòng)態(tài)分配數(shù)組的大小
};
typedef struct HeapNode* Heap;//堆的指針定義
#define MIN_DATA -20000 //這里實(shí)現(xiàn)最小堆弯囊,哨兵是最小值,堆中數(shù)據(jù)的范圍是-10000匾嘱,10000,這個(gè)值隨需要更改
創(chuàng)建一個(gè)heapsize大小的堆
Heap creatHeap(int heapsize)
{
    Heap heap;
    heap=(Heap)malloc(sizeof(struct HeapNode));
    if(heap==NULL)
    {
        printf("內(nèi)存不足");
        return NULL;
    }
    heap->data=(Element*)malloc(sizeof(Element)*(heapsize+1));
    if(heap->data==NULL)
    {
        printf("內(nèi)存不足");
        return NULL;
    }
    heap->heapsize=heapsize;
    heap->size=0;
    heap->data[0]=MIN_DATA;
    return heap;
}
判斷堆空或者堆滿

這個(gè)比較簡(jiǎn)單撬讽,因?yàn)槎咽且粋€(gè)數(shù)組悬垃,如果size變量為0,表示堆空尝蠕。如果size變量等于堆數(shù)組的最大值,那么就是堆滿看彼。

int isFull(Heap heap)
{
    return heap->size==heap->heapsize;
}
int isEmpty(Heap heap)
{
    return heap->size==0;
}
堆的插入操作

堆的插入操作就是首先將size變量加1昧捷,當(dāng)前節(jié)點(diǎn)i=size+1,然后訪問(wèn)它的父節(jié)點(diǎn)i/2罐寨,比較插入的元素和父節(jié)點(diǎn)的元素哪個(gè)元素行蚓亍(這里以最小堆為例),如果新插入的元素比較小,那么就將父節(jié)點(diǎn)i/2的值賦值到當(dāng)前節(jié)點(diǎn)i簸淀。此時(shí)將i更新(i=i/2),然后去比較此時(shí)i的節(jié)點(diǎn)的父節(jié)點(diǎn)(i/2,相當(dāng)于(size+1)/4)與插入的元素舷手,直到比較到哨兵劲绪,因?yàn)樯诒亲钚≈担圆迦氲脑貢?huì)放到數(shù)組下標(biāo)的0的位置贾富。如果比較到其中某個(gè)位置時(shí),插入的節(jié)點(diǎn)比父節(jié)點(diǎn)的大汗捡,那么就把插入的節(jié)點(diǎn)放到當(dāng)前位置畏纲。以圖說(shuō)話。

5.png

假設(shè)有如上的最小堆盗胀,堆目前的size是5,如果插入一個(gè)元素15簿训,則size加1米间,相當(dāng)于把31后面的數(shù)組位置空出來(lái),等待插入屈糊,然后比較i=6的父節(jié)點(diǎn)i/2,此時(shí)是16夫晌,因?yàn)?5小于16,所以16就需要下移到數(shù)組下標(biāo)為6的位置了晓淀。

6.png

只是將16復(fù)制到了數(shù)組下標(biāo)為6的地方,數(shù)組下標(biāo)為3的值沒(méi)有改變燥爷,還是16.然后此時(shí)i移動(dòng)到位置3懦窘,比較i=3的父節(jié)點(diǎn)i/2,此時(shí)是數(shù)組的下標(biāo)1的數(shù)據(jù)畅涂,13<15,說(shuō)明符合最小堆的定義立宜,父節(jié)點(diǎn)小于子節(jié)點(diǎn)臊岸,所以15應(yīng)該插入到數(shù)組下標(biāo)為3的地方。插入完成扇单。


7.png

在比如在此時(shí)直接插入20元素蜘澜,size加1等于7施流,它的父節(jié)點(diǎn)的值為15鄙信,比20小,所以20直接放到數(shù)組下標(biāo)為7的位置银受。

插入操作的程序如下:

void insert(Heap heap,Element x)
{
    int i=0;
    if(isFull(heap))
    {
        printf("堆滿了");
        return;
    }
    for(i=++heap->size;x<heap->data[i/2];i=i/2) //這里因?yàn)橛猩诒徊桑援?dāng)i等于0時(shí),是一定會(huì)結(jié)束循環(huán)的渔伯,如果不使用哨兵,需要判斷一下i為0选浑,否則導(dǎo)致死循環(huán)
    {
        heap->data[i]=heap->data[i/2];
    }
    heap->data[i]=x;
}
堆的刪除操作

堆的刪除操作就是將數(shù)組的下標(biāo)1的數(shù)據(jù)返回,然后重新構(gòu)造堆古徒,將size減1,并將最后一個(gè)數(shù)據(jù)找到合理的位置插入代态。就是從根節(jié)點(diǎn)開始(也就是下標(biāo)1)找到兩個(gè)子節(jié)點(diǎn)的最小值舀寓,與最后一個(gè)數(shù)據(jù)比較肌蜻,如果最后一個(gè)數(shù)據(jù)比兩個(gè)子節(jié)點(diǎn)的最小值還要小的話,那么就把這個(gè)數(shù)據(jù)復(fù)制到這兩個(gè)子節(jié)點(diǎn)的父節(jié)點(diǎn)蒋搜;如果最后一個(gè)數(shù)據(jù)比兩個(gè)子節(jié)點(diǎn)的最小值還要大的話,那么就將兩個(gè)子節(jié)點(diǎn)的最小值節(jié)點(diǎn)復(fù)制到它的父節(jié)點(diǎn)育谬,然后以這個(gè)子節(jié)點(diǎn)作為父節(jié)點(diǎn)帮哈,重復(fù)上述過(guò)程,直到找到合適位置娘侍。

7.png

假設(shè)執(zhí)行刪除操作,13刪除嚎杨,最后一個(gè)元素16被取出氧腰,size減1,比較13的子節(jié)點(diǎn)21和15古拴,15位最小的,那么比較15和16膏潮,發(fā)現(xiàn)15小满力,所以將15復(fù)制到13的位置轻纪。

8.png

此時(shí)以數(shù)組下標(biāo)3作為父節(jié)點(diǎn)叠纷,比較它的兩個(gè)子節(jié)點(diǎn),但是發(fā)現(xiàn)已經(jīng)沒(méi)有了涩嚣,所以把16復(fù)制到數(shù)組下標(biāo)3的位置就完成了刪除操作。

假設(shè)第一次刪除13時(shí)顷歌,它的兩個(gè)子節(jié)點(diǎn)都比16大幔睬,那么直接將16復(fù)制到數(shù)組下標(biāo)為1的地方,就完成了刪除麻顶。這就是堆的刪除操作的執(zhí)行過(guò)程。

程序代碼如下:

Element deleteHeap(Heap heap)
 {
    int child,parent;
    Element x,num;
    if(isEmpty(heap))
    {
        printf("堆空");
        return MIN_DATA;
    }
    x=heap->data[1];//保存要?jiǎng)h除的元素队萤,作為返回值
    num=heap->data[heap->size--];//堆的尺寸小1矫钓,將這個(gè)數(shù)值保存以免丟失
    for(parent=1;parent*2<=heap->size;parent=child)
    {
        child=2*parent;
        //尋找兩個(gè)子節(jié)點(diǎn)中最小的一個(gè),&&前面的判斷是防止child已經(jīng)是堆的最后一個(gè)數(shù)據(jù)新娜,加1后超出堆的范圍
        if((child+1)<=heap->size&&heap->data[child]>heap->data[child+1])
        {
            child=child+1;
        }
        //比較兩個(gè)子節(jié)點(diǎn)的最小值和要原堆中最后一個(gè)元素,如果最后一個(gè)元素小匆帚,說(shuō)明找到了插入的位置旁钧,結(jié)束循環(huán)
        if(num<=heap->data[child])
        {
            break;
        }
        else
        {
            heap->data[parent]=heap->data[child];
        }
    }
    heap->data[parent]=num;
    return x;
 }
堆的構(gòu)建

將一個(gè)數(shù)組變成最小堆,思路就是從根本一個(gè)子樹一個(gè)子樹的逐漸變換歪今,假設(shè)數(shù)組大小為size,那么它的父節(jié)點(diǎn)就是i=size/2嫉晶,以i為起點(diǎn),執(zhí)行與刪除操作相同的下濾操作替废,直到i變成0,就完成了堆的構(gòu)建诈火,其實(shí)就是堆排序的思路状答。

void perdown(Heap heap,int d)
 {
    int child,parent;
    Element x;
    x=heap->data[d];
    for(parent=d;parent*2<=heap->size;parent=child)
    {
        child=2*parent;
        if((child+1)<=heap->size&&heap->data[child]>heap->data[child+1])
        {
            child=child+1;
        }
        if(x<=heap->data[child])
        {
            break;
        }
        else
        {
            heap->data[parent]=heap->data[child];
        }
    }
    heap->data[parent]=x;
 }
 void buildHeap(Heap heap)
 {
     int i=0;
     for(i=heap->size/2;i>0;i--)
     {
        perdown(heap,i);
     }
 }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市拍摇,隨后出現(xiàn)的幾起案子馆截,更是在濱河造成了極大的恐慌,老刑警劉巖孙咪,帶你破解...
    沈念sama閱讀 210,835評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巡语,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡荤堪,警方通過(guò)查閱死者的電腦和手機(jī)枢赔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)碎赢,“玉大人,你說(shuō)我怎么就攤上這事肮塞∫鏊” “怎么了?”我有些...
    開封第一講書人閱讀 156,481評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵拷窜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我篮昧,道長(zhǎng),這世上最難降的妖魔是什么恋谭? 我笑而不...
    開封第一講書人閱讀 56,303評(píng)論 1 282
  • 正文 為了忘掉前任疚颊,我火速辦了婚禮,結(jié)果婚禮上材义,老公的妹妹穿的比我還像新娘。我一直安慰自己其掂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,375評(píng)論 5 384
  • 文/花漫 我一把揭開白布深寥。 她就那樣靜靜地躺著贤牛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪殉簸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,729評(píng)論 1 289
  • 那天武鲁,我揣著相機(jī)與錄音蝠检,去河邊找鬼。 笑死叹谁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的排拷。 我是一名探鬼主播锅尘,決...
    沈念sama閱讀 38,877評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼布蔗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼浪腐!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起议街,我...
    開封第一講書人閱讀 37,633評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤特漩,失蹤者是張志新(化名)和其女友劉穎吧雹,沒(méi)想到半個(gè)月后涂身,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,088評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡丁鹉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,443評(píng)論 2 326
  • 正文 我和宋清朗相戀三年悴能,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冯凹。...
    茶點(diǎn)故事閱讀 38,563評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡记靡,死狀恐怖团驱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嚎花,我是刑警寧澤,帶...
    沈念sama閱讀 34,251評(píng)論 4 328
  • 正文 年R本政府宣布啼止,位于F島的核電站兵罢,受9級(jí)特大地震影響献烦,放射性物質(zhì)發(fā)生泄漏卖词。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,827評(píng)論 3 312
  • 文/蒙蒙 一即横、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧东囚,春花似錦、人聲如沸桨嫁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)弥鹦。三九已至,卻和暖如春彬坏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背务冕。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工幻赚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人落恼。 一個(gè)月前我還...
    沈念sama閱讀 46,240評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像戴涝,于是被迫代替她去往敵國(guó)和親钻蔑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子啥刻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,435評(píng)論 2 348