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) 虛擬進程地址空間中的堆是兩回事,一個是數(shù)據(jù)結(jié)構(gòu),一個是操作系統(tǒng)中管理內(nèi)存的一塊區(qū)域分段。
其中堆中包括以下主要功能:
1.堆的初始化 2.堆銷毀 3.堆打印 4.堆的插入元素 5.堆刪除元素 6.判斷堆是否為空 7.求堆中元素的個數(shù) 8.求堆頂元素
詳細接口如下:
//小堆
//算法邏輯思想是二叉樹,物理上操作的是數(shù)組中數(shù)據(jù)
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a; //數(shù)組a
size_t size; //下標
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 *
堆的元素插入是堆的一大重點和難點。接下來我們對該功能進行分析和實現(xiàn)。
功能分析:
1、我們要向堆中插入元素,我們首先要判斷數(shù)組是否空間已滿,如果空間已滿就要擴容。擴容后再將新元素插入數(shù)組尾部。此過程和順序表相同。
2、由于插入新元素,我們要對該元素進行分析(此處以如下圖小堆舉例),分析插入元素是否會破壞堆結(jié)構(gòu),如果破壞了堆,我們就要對堆進行向上調(diào)整。

3、向上調(diào)整過程分析(過程步驟如下圖):
a. 我們發(fā)現(xiàn)出入新元素10之后,10作為28(父節(jié)點)的子節(jié)點卻比28小,這樣就破壞了我們的堆結(jié)構(gòu),這樣就不構(gòu)成小堆。因此我們需要對該結(jié)構(gòu)進行調(diào)整。
b.由于堆的物理結(jié)構(gòu)是一個數(shù)組,所以我們可以通過數(shù)組下標的形式找到我們孩子節(jié)點的父節(jié)點。不難分析出parent = (child-1)/2.當(dāng)我們找到父節(jié)點時,我們進行大小比較,如果子節(jié)點小于父節(jié)點,此時就要進行交換元素。再讓子節(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);
//考慮是否擴容
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ù),再進行向下調(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ù),再進行向下調(diào)整算法。
2、向下調(diào)整算法具體步驟(過程步驟如下圖):
a.我們將堆頂元素和數(shù)組最后一個元素交換后,此時堆頂?shù)脑厥菙?shù)組的最后一個元素,我們要進行向下調(diào)整。定義parent為堆頂元素,查找2個子節(jié)點中較小的一個節(jié)點作為孩子節(jié)點。由于堆是數(shù)組結(jié)構(gòu)實現(xiàn),我們可以首先找到左孩子節(jié)點child = 2*parent+1。讓左孩子和右孩子進行比較,較小的作為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)用:堆排序 ***
堆排序即利用堆的思想來進行排序,總共分為兩個步驟: 1. 建堆 升序:建大堆 降序:建小堆 2. 利用堆刪除思想來進行排序 建堆和堆刪除中都用到了向下調(diào)整,因此掌握了向下調(diào)整,就可以完成堆排序。
假設(shè)此時我們需要對數(shù)組元素進行升序排序,我們就可以使用我們剛剛實現(xiàn)的小堆。
4.1 堆排序?qū)崿F(xiàn)過程分析
1、首先我們將數(shù)組的元素插入到堆中,根據(jù)向上調(diào)整,此時堆已經(jīng)是小堆。
2、根據(jù)小堆的性質(zhì),堆頂?shù)脑匾欢ㄊ窃摱阎凶钚〉脑?,因此我們?nèi)〉蕉秧數(shù)脑?,再刪除堆頂?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++編譯時運行報錯source file not compile問題
這篇文章主要介紹了Dev C++編譯時運行報錯source file not compile問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
C語言數(shù)據(jù)結(jié)構(gòu)之vector底層實現(xiàn)機制解析
向量(Vector)是一個封裝了動態(tài)大小數(shù)組的順序容器(Sequence?Container)。跟任意其它類型容器一樣,它能夠存放各種類型的對象??梢院唵蔚恼J為,向量是一個能夠存放任意類型的動態(tài)數(shù)組2021-11-11
C++使用MySQL-Connector/C++連接MySQL出現(xiàn)LNK2019錯誤的解決方法
這篇文章主要介紹了C++使用MySQL-Connector/C++連接MySQL出現(xiàn)LNK2019錯誤的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-03-03

