??個人主頁??
?個人專欄——數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)?
??點擊關(guān)注??一起學(xué)習(xí)C語言????
導(dǎo)讀:
我們在前面學(xué)習(xí)了單鏈表和順序表,以及棧和隊列。
今天我們來學(xué)習(xí)小堆。
關(guān)注博主或是訂閱專欄,掌握第一消息。
1. 堆的概念及結(jié)構(gòu)
現(xiàn)實中我們通常把堆(一種二叉樹)使用順序結(jié)構(gòu)的數(shù)組來存儲,需要注意的是這里的堆和操作系統(tǒng)虛擬進程地址空間中的堆是兩回事,一個是數(shù)據(jù)結(jié)構(gòu),一個是操作系統(tǒng)中管理內(nèi)存的一塊區(qū)域分段。
1.1 什么是堆
堆是一種特殊的數(shù)據(jù)結(jié)構(gòu),它可以看做是一個完全二叉樹(或者近似二叉樹),其中每個節(jié)點的值都大于等于(或小于等于)其子節(jié)點的值。在一個最大堆中,根節(jié)點的值是最大的;在一個最小堆中,根節(jié)點的值是最小的。
1.2 堆的特點
堆的主要特點是:每個節(jié)點的值都大于等于(或小于等于)其子節(jié)點的值。這種特點使得堆可以快速找到最大(或最小)的元素。另外,堆還可以用于排序和優(yōu)先隊列等應(yīng)用。
堆中兄弟節(jié)點的值之間沒有關(guān)聯(lián)。在堆中,節(jié)點之間的關(guān)系僅由其在樹中的位置決定。
1.3 堆的結(jié)構(gòu)
堆通常使用數(shù)組來實現(xiàn),數(shù)組的下標(biāo)代表節(jié)點在堆中的位置。根據(jù)節(jié)點在數(shù)組中的位置,可以通過簡單的計算得到其父節(jié)點、左子節(jié)點和右子節(jié)點的位置。這樣,在堆中插入一個新元素、刪除堆頂?shù)脑鼗蛘哒{(diào)整堆的結(jié)構(gòu)時,只需要對數(shù)組進行簡單的操作,而不需要改變整個堆的結(jié)構(gòu)。
2. 堆的實現(xiàn)
我們需要創(chuàng)建兩個 C文件: study.c 和 Heap.c,以及一個 頭文件: Heap.h。
頭文件來聲明函數(shù),一個C文件來定義函數(shù),另外一個C文件來用于主函數(shù)main()進行測試。
堆的常見操作包括插入元素、刪除堆頂元素、堆化(調(diào)整堆的結(jié)構(gòu)使其滿足堆的特點)等。其中,插入元素和刪除堆頂元素的時間復(fù)雜度為O(logn),堆化的時間復(fù)雜度為O(nlogn)。
3. 代碼實現(xiàn)
3.1 定義結(jié)構(gòu)體
Heap.h:
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size; //記錄數(shù)組內(nèi)的有效數(shù)據(jù)
int capacity; //記錄數(shù)組空間大小
}HP;
3.2 堆的初始化
Heap.h:
//堆的初始化
void HeapInit(HP* php);
Heap.c:
//堆的初始化
void HeapInit(HP* php)
{
//各值初始化為0
assert(php);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
3.3 堆的銷毀
我們的數(shù)組空間是用malloc函數(shù)開辟的,使用完之后需要進行釋放。
Heap.h:
// 堆的銷毀
void HeapDestroy(HP* php);
Heap.c:
// 堆的銷毀
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
3.4 向上調(diào)整父節(jié)點與子節(jié)點
在出入數(shù)組后,我們需要對數(shù)組進行調(diào)整,以實現(xiàn)堆的結(jié)構(gòu)特點。
在數(shù)組中,下標(biāo)*2+1就是他的子節(jié)點,同樣的下標(biāo)-1/2就是他的父節(jié)點。
Heap.h:
//向上調(diào)整父節(jié)點與子節(jié)點
void AdjustUp(HPDataType* a, int child);
Heap.c:
//交換父節(jié)點和子節(jié)點的值
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
//向上調(diào)整父節(jié)點與子節(jié)點
void AdjustUp(HPDataType* a, int child)
{
assert(a);
int parent = (child - 1) / 2;//找到父節(jié)點
while (child > 0)
{
//查看父親節(jié)點與孩子節(jié)點的值
//若小則替換,否則就結(jié)束循環(huán)
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
3.5 堆的插入
Heap.h:
// 堆的插入
void HeapPush(HP* php, HPDataType x);
Heap.c:
// 堆的插入
// 堆的插入
void HeapPush(HP* php, HPDataType x)
{
assert(php);
//首先檢查數(shù)組容量是否足夠
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)
{
perror("realloc fail");
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
php->a[php->size] = x;
//在插入數(shù)值后需要檢查是否要進行調(diào)整
AdjustUp(php->a, php->size);
php->size++;
}
3.6 向下調(diào)整父節(jié)點與子節(jié)點
刪除堆是刪除堆頂?shù)臄?shù)據(jù),但是我們無法直接刪除第一個元素,這有極大的可能會使我們的堆崩潰,不再具有堆的特點,而在刪除之后把其它數(shù)值都往前移動,再進行調(diào)整是一項很大的工作量。
所以我們可以將堆頂?shù)臄?shù)據(jù)根最后一個數(shù)據(jù)一換,然后刪除數(shù)組最后一個數(shù)據(jù),再進行向下調(diào)整算法。將當(dāng)前的根數(shù)值調(diào)整到符合堆特點的位置去。
Heap.h:
//向下調(diào)整父節(jié)點與子節(jié)點
void AdjustDown(int* a, int size, int parent);
Heap.c:
//向下調(diào)整父節(jié)點與子節(jié)點
void AdjustDown(int* a, int size, int parent)
{
assert(a);
int child = parent * 2 + 1;//找到孩子節(jié)點
while (child < size)
{
//找孩子中較小的一個
if ((a[child] > a[child + 1]) && (child + 1) < size)
{
child += 1;
}
//判斷兩個大小進行交換
if (a[parent] > a[child])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
3.7 堆的刪除
Heap.h:
// 堆的刪除
void HeapPop(HP* php);
Heap.c:
// 堆的刪除
void HeapPop(HP* php)
{
assert(php);
//先檢查數(shù)組是否有可刪除的數(shù)據(jù)
assert(php->size > 0);
//交換首尾元素
Swap(&php->a[php->size - 1], &php->a[0]);
php->size--;
//向下進行調(diào)整
AdjustDown(php->a, php->size, 0);
}
3.8 獲取堆頂元素
返回數(shù)組首元素即可
Heap.h:
// 取堆頂?shù)臄?shù)據(jù)
HPDataType HeapTop(HP* php);
Heap.c:
// 取堆頂?shù)臄?shù)據(jù)
HPDataType HeapTop(HP* php)
{
assert(php);
return php->a[0];
}
3.9 獲取堆的個數(shù)
直接返回size的值即可
Heap.h:
// 堆的數(shù)據(jù)個數(shù)
size_t HeapSize(HP* php);
Heap.c:
// 堆的數(shù)據(jù)個數(shù)
size_t HeapSize(HP* php)
{
assert(php);
return php->size;
}
3.10 堆的判空
只需判斷size的值是否為0,如果是,返回true,反之返回false。
Heap.h:
// 堆的判空
bool HeapEmpty(HP* php);
Heap.c:文章來源:http://www.zghlxwxcb.cn/news/detail-762343.html
// 堆的判空
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
4. 代碼整理
4.1 Heap.h
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size; //記錄數(shù)組內(nèi)的有效數(shù)據(jù)
int capacity; //記錄數(shù)組空間大小
}HP;
//堆的初始化
void HeapInit(HP* php);
// 堆的銷毀
void HeapDestroy(HP* php);
// 堆的插入
void HeapPush(HP* php, HPDataType x);
// 堆的刪除
void HeapPop(HP* php);
// 取堆頂?shù)臄?shù)據(jù)
HPDataType HeapTop(HP* php);
// 堆的數(shù)據(jù)個數(shù)
size_t HeapSize(HP* php);
// 堆的判空
bool HeapEmpty(HP* php);
//向上調(diào)整父節(jié)點與子節(jié)點
void AdjustUp(HPDataType* a, int child);
//向下調(diào)整父節(jié)點與子節(jié)點
void AdjustDown(int* a, int size, int parent);
//交換父節(jié)點和子節(jié)點的值
void Swap(int* child, int* parent);
4.2 Heap.c
#include "Heap.h"
//堆的初始化
void HeapInit(HP* php)
{
//各值初始化為0
assert(php);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
// 堆的銷毀
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
//交換父節(jié)點和子節(jié)點的值
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
//向上調(diào)整父節(jié)點與子節(jié)點
void AdjustUp(HPDataType* a, int child)
{
assert(a);
int parent = (child - 1) / 2;//找到父節(jié)點
while (child > 0)
{
//查看父親節(jié)點與孩子節(jié)點的值
//若小則替換,否則就結(jié)束循環(huán)
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);
//首先檢查數(shù)組容量是否足夠
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)
{
perror("realloc fail");
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
php->a[php->size] = x;
//在插入數(shù)值后需要檢查是否要進行調(diào)整
AdjustUp(php->a, php->size);
php->size++;
}
//向下調(diào)整父節(jié)點與子節(jié)點
void AdjustDown(int* a, int size, int parent)
{
assert(a);
int child = parent * 2 + 1;//找到孩子節(jié)點
while (child < size)
{
//找孩子中較小的一個
if ((a[child] > a[child + 1]) && (child + 1) < size)
{
child += 1;
}
//判斷兩個大小進行交換
if (a[parent] > a[child])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
// 堆的刪除
void HeapPop(HP* php)
{
assert(php);
//先檢查數(shù)組是否有可刪除的數(shù)據(jù)
assert(php->size > 0);
//交換首尾元素
Swap(&php->a[php->size - 1], &php->a[0]);
php->size--;
//向下進行調(diào)整
AdjustDown(php->a, php->size, 0);
}
// 取堆頂?shù)臄?shù)據(jù)
HPDataType HeapTop(HP* php)
{
assert(php);
return php->a[0];
}
// 堆的數(shù)據(jù)個數(shù)
size_t HeapSize(HP* php)
{
assert(php);
return php->size;
}
// 堆的判空
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
4.3 study.c
void Test1()
{
int array[] = { 27,15,19,18,28,34,65,49,25,37 };
HP hp;
HeapInit(&hp);
for (int i = 0; i < sizeof(array) / sizeof(int); i++)
{
HeapPush(&hp, array[i]);//插入數(shù)據(jù)
}
int k = HeapSize(&hp);
while (k--)
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
HeapDestroy(&hp);
}
int main()
{;
Test1();
return 0;
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-762343.html
到了這里,關(guān)于【C語言】數(shù)據(jù)結(jié)構(gòu)——小堆實例探究的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!