C語言植物大戰(zhàn)數(shù)據(jù)結(jié)構(gòu)二叉樹堆
“竹杖芒鞋輕勝馬,誰怕?一蓑煙雨任平生”
C語言朱武大戰(zhàn)數(shù)據(jù)結(jié)構(gòu)專欄
C語言植物大戰(zhàn)數(shù)據(jù)結(jié)構(gòu)快速排序圖文示例
C語言植物大戰(zhàn)數(shù)據(jù)結(jié)構(gòu)希爾排序算法
C語言植物大戰(zhàn)數(shù)據(jù)結(jié)構(gòu)堆排序圖文示例
C語言植物大戰(zhàn)數(shù)據(jù)結(jié)構(gòu)二叉樹遞歸
前言
此篇詳細(xì)實(shí)現(xiàn)二叉樹中的一個(gè)順序結(jié)構(gòu)的實(shí)現(xiàn)——堆。并實(shí)現(xiàn)堆排序重點(diǎn)是堆的規(guī)律和堆的實(shí)現(xiàn)
堆的概念
堆:將一些元素按完全二叉樹的順序存儲(chǔ)方式存儲(chǔ)在一個(gè)一維數(shù)組中。這就是堆。完全二叉樹:通俗講就是只有最后一層的節(jié)點(diǎn)可以滿,也可以不滿。但是最后一層之前的節(jié)點(diǎn)要滿,這就叫做完全二叉樹。
小堆大堆:將根節(jié)點(diǎn)最大的堆叫做最大堆或大根堆,根節(jié)點(diǎn)最小的堆叫做最小堆或小根堆。(必須滿足堆的性質(zhì))
堆的性質(zhì):
1.堆中某個(gè)節(jié)點(diǎn)的值總是不大于或不小于其父節(jié)點(diǎn)的值
2.堆總是一棵完全二叉樹。
注意:
1.普通的二叉樹是不適合用數(shù)組來存儲(chǔ)的,因?yàn)榭赡軙?huì)存在大量的空間浪費(fèi)。而完全二叉樹更適合使用順序結(jié)構(gòu)存儲(chǔ)。
2.現(xiàn)實(shí)中我們通常把堆(一種特殊的二叉樹)使用順序結(jié)構(gòu)的數(shù)組來存儲(chǔ)。
3.需要注意的是這里的堆和操作系統(tǒng)虛擬進(jìn)程地址空間中的堆是兩回事,一個(gè)是數(shù)據(jù)結(jié)構(gòu),一個(gè)是操作系統(tǒng)中管理內(nèi)存的一塊區(qū)域分段。
接下來我們用純C實(shí)現(xiàn)小堆
創(chuàng)建結(jié)構(gòu)體
因?yàn)橥耆鏄涓m合使用順序結(jié)構(gòu)存儲(chǔ)。而堆就是完全二叉樹,所以我們需要?jiǎng)?chuàng)建順序表
typedef int HPDataType; typedef struct Heap { HPDataType* a;//a指向只一個(gè)堆數(shù)組 size_t size;//記錄堆的大小 size_t capacity;//堆的最大容量 }HP;
初始化結(jié)構(gòu)體
這一步必須要有,相當(dāng)于創(chuàng)建變量了。
//初始化結(jié)構(gòu)體 void HeapInit(HP* php) { assert(php); php->a = NULL; php->size = 0; php->capacity = 0; }
銷毀結(jié)構(gòu)體
因?yàn)槭莿?dòng)態(tài)版的順序表,有動(dòng)態(tài)開辟就需要?jiǎng)討B(tài)內(nèi)存釋放,也就是free掉a所指向的空間。
//銷毀結(jié)構(gòu)體 void HeapDestroy(HP* php) { assert(php); free(php->a); //free掉及時(shí)置空,防止野指針 php->a = NULL; php->size = 0; php->capacity = 0; }
向堆中插入數(shù)據(jù)
在插入數(shù)據(jù)之前,我們需要先知道堆的一個(gè)技巧。
1.堆的物理結(jié)構(gòu)和邏輯結(jié)構(gòu)
前面的步驟打造了一個(gè)堆的輪廓,你說它是堆吧,其實(shí)本質(zhì)就是一個(gè)物理結(jié)構(gòu)在內(nèi)存中連續(xù)的順序表罷了。也就是數(shù)組。如圖下標(biāo)從0開始。
但是要用到它的邏輯結(jié)構(gòu),需要把它想象成一個(gè)完全二叉樹,就是下面圖片這個(gè)樣子。
好了,現(xiàn)在我們要向堆中插入數(shù)據(jù)了,因?yàn)轫樞虮砦膊逍矢逴(1),且尾插不會(huì)大幅度改變堆的結(jié)構(gòu)。所以插入數(shù)據(jù)指的是尾插。
2.完全二叉樹下標(biāo)規(guī)律
注意:
1.尾插注意的是size的大小,size一直指向的是即將插入的那個(gè)空間。
2.和順序表唯一不同的是尾插后需要向上調(diào)整數(shù)據(jù),保持小堆的從上往下依次增大的結(jié)構(gòu)
3.堆中的下標(biāo)是有規(guī)律的。規(guī)律公式如下
這里需要強(qiáng)調(diào)的是對(duì)于父親下標(biāo)的計(jì)算。父親的下標(biāo)計(jì)算公式為:parent = (child - 1) / 2;舉個(gè)例子。因?yàn)榧偃缱笞訕涫?,右子樹是8。7-1初以2是3, 但8-1初以2是3.5。但是計(jì)算機(jī)計(jì)算的結(jié)果也是3。
結(jié)論:所以管他是左子樹,還是右子樹,都能計(jì)算出其父親的下標(biāo)。
3.插入數(shù)據(jù)思路
最重要的思路是向上調(diào)整思想
我們以向堆中插入一個(gè)8為例。
因?yàn)閟ize指向的是順序表中即將插入的位置,所以直接插入就好了,但要及時(shí)讓size++。
注意:尾插后size還需要指向下一個(gè)即將插入的位置。如圖
然后開始向上調(diào)整8的位置。
思路:
1.讓child依次和其parent比較,假如child小的話就交換兩者的數(shù)據(jù),使child的值依次向上走。然后迭代執(zhí)行child = parent;parent = (child - 1) / 2;用來迭代parent和child的位置,直到child等于0。結(jié)束循環(huán)。
2.我覺得思路不難,難在把思路轉(zhuǎn)換為代碼然后實(shí)現(xiàn)。
3.注意實(shí)參傳遞的實(shí)參分別是數(shù)組首元素的地址,和新插入元素的下標(biāo)。
//交換 void HeapSwap(HPDataType* pa, HPDataType* pb) { HPDataType tmp = *pa; *pa = *pb; *pb = tmp; } //向上調(diào)整 void AdjustUp(HPDataType* a, size_t child) { size_t parent = (child - 1) / 2; while (child > 0) { if (a[child] < a[parent]) { //因?yàn)樾枰啻斡玫浇粨Q算法,所以寫成一個(gè)函數(shù),方便調(diào)用。 HeapSwap(&a[child], &a[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } } //插入堆 void HeapPush(HP* php, HPDataType x) { assert(php); //除了AdjustUp if (php->size == php->capacity) { int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2; //注意realloc的用法 HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType)* newcapacity); if (tmp == NULL) { printf("realloc fail\n"); exit(-1); } php->a = tmp; php->capacity = newcapacity; } php->a[php->size] = x; ++php->size; //唯一不同于順序表的地方,向上調(diào)整算法 AdjustUp(php->a, php->size - 1); }
依次打印堆的值
插入堆后,為了可視化,我們還是打印一下看看效果。
void HeapPrint(HP* php) { assert(php); for (size_t i = 0; i < php->size; ++i) { printf("%d ", php->a[i]); } printf("\n"); }
刪除堆頂?shù)闹?/h2>
刪除也是一門藝術(shù)。今天的我是站在巨人的肩膀上學(xué)習(xí)堆的刪除。
大佬們的算法思路是:
1.先讓堆頂?shù)闹岛投盐驳闹到粨QO(1),然后刪除O(1)交換后堆尾的值。
2.再使用向下調(diào)整O(logN)算法,先比較left和right哪個(gè)小,誰小就讓parent和誰交換,然后依次迭代,使堆頂?shù)闹迪蛳抡{(diào)整。直到child大于size結(jié)束循環(huán)。
因?yàn)檫@樣的時(shí)間復(fù)雜度是相當(dāng)牛的。
注意:這里有幾個(gè)地方代碼技巧非常絕。
1.假設(shè)左子樹就是最小的值。然后比較左右子樹的大小后進(jìn)行調(diào)整到底誰是最小的就OK了。這是非常的絕。因?yàn)槟阈枰P(guān)注到底是左子樹小還是右子樹??!
//非常絕的一個(gè)思路 if (child+1 < size && a[child + 1] < a[child]) { ++child; }
刪除代碼如下。
//向下調(diào)整 AdjustDown(HPDataType* a,size_t size, size_t root) { size_t parent= root; size_t child= parent * 2 + 1; while (child < size) { //判斷哪個(gè)孩子小。 if (child+1 < size && a[child + 1] < a[child]) { ++child; } //交換父親和孩子的值 if (a[child] < a[parent]) { HeapSwap(&a[parent], &a[child]); //迭代 parent = child; child = parent * 2 + 1; } else { break; } } } //刪除堆頂?shù)闹? void HeapPop(HP* php) { assert(php); assert(php->size > 0); //交換堆頂和堆尾的值。然后刪除堆尾的值 HeapSwap(php->a, &php->a[php->size - 1]); --php->size; //向下調(diào)整 AdjustDown(php->a, php->size, 0); }
判斷堆是否為空
當(dāng)size為0時(shí),堆即為空。
bool HeapEmpty(HP* php) { assert(php); return php->size == 0; }
求堆中有幾個(gè)元素
size標(biāo)記的就是有幾個(gè)元素。
//求堆中有幾個(gè)元素 size_t HeapSize(HP* php) { assert(php); return php->size; }
得到堆頂?shù)闹?/h2>
需要注意的是size最少為1.當(dāng)size為0時(shí),就意味著堆已經(jīng)為空。所以我們需要assert斷言size不為0.
HPDataType HeapTop(HP* php) { assert(php); assert(php->size > 0); return php->a[0]; }
堆排序
堆排序即利用堆的思想來進(jìn)行排序,總共分為兩個(gè)步驟:
建堆
升序:建大堆
降序:建小堆(我們這里用的是建小堆)利用堆刪除思想來進(jìn)行排序
void HeapSort(HPDataType* a, int size) { HP hp; HeapInit(&hp); //先創(chuàng)建一個(gè)小堆 for (int i = 0; i < size; i++) { HeapPush(&hp, a[i]); } size_t j = 0; //利用堆刪除思想來進(jìn)行排序 while (!HeapEmpty(&hp)) { a[j] = HeapTop(&hp); j++; HeapPop(&hp); } HeapDestroy(&hp); } int main() { //對(duì)a數(shù)組進(jìn)行排序 int a[] = { 4,2,7,8,5,1,0,6 }; HeapSort(a, sizeof(a) / sizeof(int)); for (int i = 0; i < sizeof(a) / sizeof(int); ++i) { printf("%d ", a[i]); } printf("\n"); return 0; }
時(shí)間復(fù)雜度分析:
時(shí)間復(fù)雜度為O(N*logN)。比冒泡排序上升了一個(gè)檔次哦。
但是空間復(fù)雜度有待改進(jìn)。
但是空間復(fù)雜度因?yàn)檎加昧艘粋€(gè)數(shù)組所以是O(N)。
空間復(fù)雜度改進(jìn)的話需要很多字來詳解。下篇博文繼續(xù)敘述。
總體代碼
Heap.h
#define _CRT_SECURE_NO_WARNINGS #pragma once #include <stdio.h> #include <assert.h> #include <stdlib.h> #include <stdbool.h> typedef int HPDataType; typedef struct Heap { HPDataType* a; size_t size; size_t capacity; }HP; //初始化結(jié)構(gòu)體 void HeapInit(HP* php); //銷毀結(jié)構(gòu)體 void HeapDestroy(HP* php); //向堆里插入數(shù)據(jù) void HeapPush(HP* php, HPDataType x); //交換堆中父子 void HeapSwap(HPDataType* pa, HPDataType* pb); //刪除堆頂數(shù)據(jù) void HeapPop(HP* php); //按照下標(biāo)打印堆 void HeapPrint(HP* php); //判斷堆是否為空 bool HeapEmpty(HP* php); //求堆中有幾個(gè)元素 size_t HeapSize(HP* php); //得到堆頂?shù)闹? HPDataType HeapTop(HP* php);
Heap.c
#define _CRT_SECURE_NO_WARNINGS #include "Heap.h" //初始化結(jié)構(gòu)體 void HeapInit(HP* php) { assert(php); php->a = NULL; php->size = 0; php->capacity = 0; } //銷毀結(jié)構(gòu)體 void HeapDestroy(HP* php) { assert(php); free(php->a); php->a = NULL; php->size = 0; php->capacity = 0; } //交換 void HeapSwap(HPDataType* pa, HPDataType* pb) { HPDataType tmp = *pa; *pa = *pb; *pb = tmp; } //向上調(diào)整 void AdjustUp(HPDataType* a, size_t child) { size_t parent = (child - 1) / 2; while (child > 0) { if (a[child] < a[parent]) { HeapSwap(&a[child], &a[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } } //插入堆 void HeapPush(HP* php, HPDataType x) { assert(php); if (php->size == php->capacity) { int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2; HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType)* newcapacity); if (tmp == NULL) { printf("realloc fail\n"); exit(-1); } php->a = tmp; php->capacity = newcapacity; } php->a[php->size] = x; ++php->size; //向上調(diào)整 AdjustUp(php->a, php->size - 1); } //向下調(diào)整 AdjustDown(HPDataType* a,size_t size, size_t root) { size_t parent= root; size_t child= parent * 2 + 1; while (child < size) { if (child+1 < size && a[child + 1] < a[child]) { ++child; } if (a[child] < a[parent]) { HeapSwap(&a[parent], &a[child]); parent = child; child = parent * 2 + 1; } else { break; } } } //刪除堆的值 void HeapPop(HP* php) { assert(php); assert(php->size > 0); //交換堆頂和堆尾的值。然后刪除堆尾的值 HeapSwap(php->a, &php->a[php->size - 1]); --php->size; //向下調(diào)整 AdjustDown(php->a, php->size, 0); } //打印堆 void HeapPrint(HP* php) { assert(php); for (size_t i = 0; i < php->size; ++i) { printf("%d ", php->a[i]); } printf("\n"); } //判斷堆是否為空 bool HeapEmpty(HP* php) { assert(php); return php->size == 0; } //求堆中有幾個(gè)元素 size_t HeapSize(HP* php) { assert(php); return php->size; } //得到堆頂?shù)闹? HPDataType HeapTop(HP* php) { assert(php); assert(php->size > 0); return php->a[0]; }
Test.c
#define _CRT_SECURE_NO_WARNINGS #include "Heap.h" void testHeap() { HP hp; HeapInit(&hp); HeapPush(&hp, 5); HeapPush(&hp, 4); HeapPush(&hp, 3); HeapPush(&hp, 2); HeapPush(&hp, 1); HeapPrint(&hp); /*HeapPop(&hp); HeapPrint(&hp);*/ HeapDestroy(&hp); } void HeapSort(HPDataType* a, int size) { HP hp; HeapInit(&hp); for (int i = 0; i < size; i++) { HeapPush(&hp, a[i]); } size_t j = 0; while (!HeapEmpty(&hp)) { a[j] = HeapTop(&hp); j++; HeapPop(&hp); } HeapDestroy(&hp); } int main() { /*testHeap();*/ int a[] = { 4,2,7,8,5,1,0,6 }; HeapSort(a, sizeof(a) / sizeof(int)); for (int i = 0; i < sizeof(a) / sizeof(int); ++i) { printf("%d ", a[i]); } printf("\n"); return 0; }
以上就是C語言植物大戰(zhàn)數(shù)據(jù)結(jié)構(gòu)二叉樹堆的詳細(xì)內(nèi)容,更多關(guān)于C語言二叉樹堆的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++標(biāo)準(zhǔn)模板庫(kù)vector的常用操作
今天小編就為大家分享一篇關(guān)于C++標(biāo)準(zhǔn)模板庫(kù)vector的常用操作,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12C語言鏈表實(shí)現(xiàn)歌手評(píng)分系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語言鏈表實(shí)現(xiàn)歌手評(píng)分系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03C++采用openfilename打開文件對(duì)話框用法實(shí)例
這篇文章主要介紹了C++采用openfilename打開文件對(duì)話框用法實(shí)例,是C++文件操作中非常實(shí)用的技巧,需要的朋友可以參考下2014-10-10C++實(shí)現(xiàn)LeetCode(167.兩數(shù)之和之二 - 輸入數(shù)組有序)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(167.兩數(shù)之和之二 - 輸入數(shù)組有序),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08C++左值與右值,右值引用,移動(dòng)語義與完美轉(zhuǎn)發(fā)詳解
這篇文章主要為大家詳細(xì)介紹了Python實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03C#將Unicode編碼轉(zhuǎn)換為漢字字符串的簡(jiǎn)單方法
下面小編就為大家?guī)硪黄狢#將Unicode編碼轉(zhuǎn)換為漢字字符串的簡(jiǎn)單方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-01-01C++編程中break語句和continue語句的學(xué)習(xí)教程
這篇文章主要介紹了C++編程中break語句和continue語句的學(xué)習(xí)教程,break和continue是C++循環(huán)控制中的基礎(chǔ)語句,需要的朋友可以參考下2016-01-01