C語言數(shù)據(jù)結(jié)構(gòu)之堆、堆排序的分析及實現(xiàn)
1.堆的概念結(jié)構(gòu)及分類
以上這段概念描述看起來十分復(fù)雜,晦澀難懂。那么堆用通俗語言簡單描述如下:
堆是一個完全二叉樹的順序存儲。在一個堆中,堆的父節(jié)點一定大于等于(或小于等于)子節(jié)點。一旦有一部分不滿足則不為堆。
堆的性質(zhì):
1、堆中某個節(jié)點的值總是不大于或不小于其父節(jié)點的值;
2、堆總是一棵完全二叉樹
1.2堆的分類
1.2.1 大堆
在一個堆中,父節(jié)點一定大于等于子節(jié)點的堆稱為大堆。又稱大根堆。
1.2.2 小堆
在一個堆中,父節(jié)點一定小于等于子節(jié)點的堆稱為小堆。又稱小根堆。(下圖就是一個小堆)
習(xí)題練習(xí):
1.下列關(guān)鍵字序列為堆的是:(A)
A 100,60,70,50,32,65
B 60,70,65,50,32,100
C 65,100,70,32,50,60
D 70,65,100,32,50,60
E 32,50,100,70,65,60
F 50,100,70,65,60,32
分析:選項A分析后為大堆,其他選項多多少少都存在錯誤。(畫圖分析如下)
2. 堆的主要接口
在本篇文章中我們主要以小堆為例實現(xiàn)。
現(xiàn)實中我們通常把堆使用順序結(jié)構(gòu)的數(shù)組來存儲,需要注意的是這里的堆和操作系統(tǒng) 虛擬進(jìn)程地址空間中的堆是兩回事,一個是數(shù)據(jù)結(jié)構(gòu),一個是操作系統(tǒng)中管理內(nèi)存的一塊區(qū)域分段。
其中堆中包括以下主要功能:
1.堆的初始化 2.堆銷毀 3.堆打印 4.堆的插入元素 5.堆刪除元素 6.判斷堆是否為空 7.求堆中元素的個數(shù) 8.求堆頂元素
詳細(xì)接口如下:
//小堆 //算法邏輯思想是二叉樹,物理上操作的是數(shù)組中數(shù)據(jù) typedef int HPDataType; typedef struct Heap { HPDataType* a; //數(shù)組a size_t size; //下標(biāo) size_t capacity; //容量 }HP; void Swap(HPDataType* pa, HPDataType* pb);//交換函數(shù) void HeapInit(HP* php);//堆初始化 void HeapDestory(HP* php);//堆銷毀 void HeapPrint(HP* php);//堆打印 //插入x以后,仍然要保證堆是(大/小)堆 void HeapPush(HP* php, HPDataType x); //刪除堆頂?shù)臄?shù)據(jù)(最大/最?。? void HeapPop(HP* php); bool HeapEmpty(HP* php); //判斷是否為空 size_t HeapSize(HP* php);//求元素個數(shù) HPDataType HeapTop(HP* php);//求堆頂元素
3.堆的實現(xiàn)
有了如上的接口,接下來我們實現(xiàn)各個接口。由于我們使用數(shù)組來實現(xiàn)堆,大多接口功能和順序表的實現(xiàn)相同。相同的實現(xiàn)這里不再過多分析。
3.1 堆的初始化 HeapInit
void HeapInit(HP* php) { assert(php); php->a = NULL; php->size = php->capacity = 0; }
3.2 堆的銷毀 HeapDestory
void HeapDestory(HP* php) { assert(php); free(php->a); php->a = NULL; php->capacity = php->size = 0; }
3.3 堆的打印 HeapPrint
void HeapPrint(HP* php) { assert(php); for (size_t i = 0; i < php->size; ++i) { printf("%d ", php->a[i]); } printf("\n"); }
3.4 堆的插入元素 HeapPush *
堆的元素插入是堆的一大重點和難點。接下來我們對該功能進(jìn)行分析和實現(xiàn)。
功能分析:
1、我們要向堆中插入元素,我們首先要判斷數(shù)組是否空間已滿,如果空間已滿就要擴(kuò)容。擴(kuò)容后再將新元素插入數(shù)組尾部。此過程和順序表相同。
2、由于插入新元素,我們要對該元素進(jìn)行分析(此處以如下圖小堆舉例),分析插入元素是否會破壞堆結(jié)構(gòu),如果破壞了堆,我們就要對堆進(jìn)行向上調(diào)整。
3、向上調(diào)整過程分析(過程步驟如下圖):
a. 我們發(fā)現(xiàn)出入新元素10之后,10作為28(父節(jié)點)的子節(jié)點卻比28小,這樣就破壞了我們的堆結(jié)構(gòu),這樣就不構(gòu)成小堆。因此我們需要對該結(jié)構(gòu)進(jìn)行調(diào)整。
b.由于堆的物理結(jié)構(gòu)是一個數(shù)組,所以我們可以通過數(shù)組下標(biāo)的形式找到我們孩子節(jié)點的父節(jié)點。不難分析出parent = (child-1)/2.當(dāng)我們找到父節(jié)點時,我們進(jìn)行大小比較,如果子節(jié)點小于父節(jié)點,此時就要進(jìn)行交換元素。再讓子節(jié)點到父節(jié)點的位置,重新計算父節(jié)點。
c.持續(xù)循環(huán)比較,如果child等于0時,說明向上調(diào)整結(jié)束。因此循環(huán)的條件可寫為child>0.
注:循環(huán)過程中一旦成堆,則跳出循環(huán)。
功能實現(xiàn):
//交換函數(shù) void Swap(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]) { Swap(&a[child], &a[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } } void HeapPush(HP* php, HPDataType x) { assert(php); //考慮是否擴(kuò)容 if (php->size == php->capacity) { size_t newCapacity = php->capacity == 0 ? 4 : php->capacity * 2; HPDataType* tmp = realloc(php->a, sizeof(HPDataType) * newCapacity); if (tmp == NULL) { printf("realloc failed\n"); exit(-1); } php->a = tmp; php->capacity = newCapacity; } php->a[php->size] = x; ++php->size; //需要向上調(diào)整 AdjustUp(php->a, php->size - 1); }
3.5 堆的刪除元素 HeapPop *
刪除堆是刪除堆頂?shù)臄?shù)據(jù) 思路:將堆頂?shù)臄?shù)據(jù)跟最后一個數(shù)據(jù)一換,然后刪除數(shù)組最后一個數(shù)據(jù),再進(jìn)行向下調(diào)整算法。
功能分析:
我們要刪除堆是刪除堆頂?shù)臄?shù)據(jù),我們不能直接刪除堆頂?shù)臄?shù)據(jù)。如果直接刪除堆頂?shù)臄?shù)據(jù),就會破壞堆結(jié)構(gòu),并且復(fù)原的復(fù)雜度較高。在這里我們介紹一種方法不僅解決了刪除堆的問題,并且復(fù)雜度較低。
1、首先我們要將堆頂?shù)臄?shù)據(jù)跟最后一個數(shù)據(jù)交換,然后刪除數(shù)組最后一個數(shù)據(jù),再進(jìn)行向下調(diào)整算法。
2、向下調(diào)整算法具體步驟(過程步驟如下圖):
a.我們將堆頂元素和數(shù)組最后一個元素交換后,此時堆頂?shù)脑厥菙?shù)組的最后一個元素,我們要進(jìn)行向下調(diào)整。定義parent為堆頂元素,查找2個子節(jié)點中較小的一個節(jié)點作為孩子節(jié)點。由于堆是數(shù)組結(jié)構(gòu)實現(xiàn),我們可以首先找到左孩子節(jié)點child = 2*parent+1。讓左孩子和右孩子進(jìn)行比較,較小的作為child的最后值。
b.如果孩子小于父親,則交換,并繼續(xù)往下調(diào)整。讓parent到child的位置,再重新計算孩子。
c.當(dāng)孩子大于等于元素總個數(shù)時,循環(huán)結(jié)束。因此循環(huán)的條件可以寫為child<size.
注:循環(huán)過程中一旦成堆,則跳出循環(huán)。
功能實現(xiàn):
void AdjustDown(HPDataType* a, size_t size, size_t root) { size_t parent = root; size_t child = parent * 2 + 1;//先拿到左孩子 while (child < size) { // 1、選出左右孩子中小的那個 if (child + 1 < size && a[child + 1] < a[child]) { ++child; } // 2、如果孩子小于父親,則交換,并繼續(xù)往下調(diào)整 if (a[child] < a[parent]) { Swap(&a[child], &a[parent]); parent = child; child = parent * 2 + 1; } else { break; } } } void HeapPop(HP* php) { assert(php); assert(php->size > 0); Swap(&php->a[0], &php->a[php->size - 1]); --php->size; AdjustDown(php->a, php->size, 0); }
3.6 判斷是否為空 HeapEmpty
bool HeapEmpty(HP* php) { assert(php); return php->size == 0; }
3.7 求元素個數(shù)
size_t HeapSize(HP* php) { assert(php); return php->size; }
3.8 求堆頂元素
HPDataType HeapTop(HP* php) { assert(php); assert(php->size > 0); return php->a[0]; }
4.堆的應(yīng)用:堆排序 ***
堆排序即利用堆的思想來進(jìn)行排序,總共分為兩個步驟: 1. 建堆 升序:建大堆 降序:建小堆 2. 利用堆刪除思想來進(jìn)行排序 建堆和堆刪除中都用到了向下調(diào)整,因此掌握了向下調(diào)整,就可以完成堆排序。
假設(shè)此時我們需要對數(shù)組元素進(jìn)行升序排序,我們就可以使用我們剛剛實現(xiàn)的小堆。
4.1 堆排序?qū)崿F(xiàn)過程分析
1、首先我們將數(shù)組的元素插入到堆中,根據(jù)向上調(diào)整,此時堆已經(jīng)是小堆。
2、根據(jù)小堆的性質(zhì),堆頂?shù)脑匾欢ㄊ窃摱阎凶钚〉脑?,因此我們?nèi)〉蕉秧數(shù)脑兀賱h除堆頂?shù)脑刈尪阎匦律尚《?。依次循環(huán)即可解決升序排序(降序排序只需將小堆改為大堆即可)。
4.2 堆排序?qū)崿F(xiàn)代碼
//堆排序 void HeapSort(int* 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); } HeapDestory(&hp); } int main() { // TestHeap(); int a[] = { 4,2,1,3,5,7,9,8,6}; HeapSort(a,sizeof(a)/sizeof(int)); for (int i = 0; i < sizeof(a) / sizeof(int); ++i) { printf("%d ", a[i]); } return 0; }
4.3 堆排序結(jié)果演示
5.堆(小堆)的完整代碼
2022_03_30 -- 堆/2022_03_30 -- 二叉樹 · 李興宇/數(shù)據(jù)結(jié)構(gòu)
總結(jié)
到此這篇關(guān)于C語言數(shù)據(jù)結(jié)構(gòu)之堆、堆排序的分析及實現(xiàn)的文章就介紹到這了,更多相關(guān)C語言堆、堆排序?qū)崿F(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Dev C++編譯時運(yùn)行報錯source file not compile問題
這篇文章主要介紹了Dev C++編譯時運(yùn)行報錯source file not compile問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01C語言數(shù)據(jù)結(jié)構(gòu)之vector底層實現(xiàn)機(jī)制解析
向量(Vector)是一個封裝了動態(tài)大小數(shù)組的順序容器(Sequence?Container)。跟任意其它類型容器一樣,它能夠存放各種類型的對象??梢院唵蔚恼J(rèn)為,向量是一個能夠存放任意類型的動態(tài)數(shù)組2021-11-11C++使用MySQL-Connector/C++連接MySQL出現(xiàn)LNK2019錯誤的解決方法
這篇文章主要介紹了C++使用MySQL-Connector/C++連接MySQL出現(xiàn)LNK2019錯誤的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-03-03