要對堆進行操作首先應該清楚什麼是堆?
堆:是計算機科學中一類特殊的數據結構的統稱,堆通常是一個可以被看做一棵樹的數組對象。
堆的性質:
- 堆的某個節點的值總是不大於或不小於其父節點的值;
- 堆總是一棵完全二叉樹
堆有大堆小堆之分,根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆。常見的堆有二叉堆、斐波那契堆等。
堆的定義如下:
n個元素的序列{k1,k2,...,kn}當且僅當滿足以下關係時,稱之爲堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1),(i = 1,2,3,...n/2)
heap.c文件
#pragma once
#include<stddef.h>
#define HeapMaxSize 1024
typedef char HeapType;
//如果a和b滿足比較關係,返回1
//如果a和b不滿足比較關係,返回0
//所謂的比較關係,對於小堆來說就是a<b
//對於大堆來說,就是a>b
typedef int (*Compare)(HeapType a,HeapType b);
typedef struct Heap
{
HeapType data[HeapMaxSize];
size_t size;
Compare cmp;
}Heap;
1.堆的初始化
(1)實現函數:
int Less(HeapType a,HeapType b)//爲小堆打造的比較函數
{
return a < b ? 1 : 0;
}
int Greater(HeapType a,HeapType b)
//爲大堆打造的比較函數
{
return a > b ? 1 : 0;
}
void HeapInit(Heap* heap,Compare cmp)
{
if(heap == NULL)
{
//非法輸入
return;
}
heap->size = 0;
heap->cmp = cmp;
return;
}
(2)測試代碼:
void TestInit()
{
TEST_HEADER;
Heap heap;
HeapInit(&heap,Greater);
printf("size expected 0,actual %lu\n",heap.size);
printf("cmp expected %p,actual %p\n",Greater,heap.cmp);
}
(3)運行結果:
2.銷燬堆
(1)實現代碼:
void HeapDestroy(Heap* heap)
{
if(heap == NULL)
{
//非法輸入
return;
}
heap->size = 0;
heap->cmp = NULL;
return;
}
(2)測試代碼:
void TestDestroy()
{
TEST_HEADER;
Heap heap;
HeapDestroy(&heap);
printf("size expected 0,actual %lu\n",heap.size);
printf("cmp expected NULL,actual %p\n",heap.cmp);
}
(3)運行結果:
3.向堆中插入元素
思想:先將要插入的元素插入到堆的最後一個位置上然後進行上浮式調整
(1)實現代碼:
void Swap(HeapType* a,HeapType* b)
{
HeapType tmp = *a;
*a = *b;
*b = tmp;
return;
}
void AdjustUp(Heap* heap,size_t index)
{
if(heap == NULL)
{
return;
}
//一個變量只做一件事
size_t child = index;
size_t parent = (child -1)/2;
while(child > 0)
{
if(!heap->cmp(heap->data[parent],heap->data[child]))
{
Swap(&heap->data[parent],&heap->data[child]);
}
else
{
//如果發現某個位置下,child 和 parent 已經滿足堆的要求
//此時就可以停止上浮
//因爲更上面的節點一定也是滿足堆的要求的
break;
}
child = parent;
parent = (child -1)/2;
}
return;
}
void HeapInsert(Heap* heap,HeapType value)
{
if(heap == NULL)
{
//非法輸入
return;
}
if(heap->size >= HeapMaxSize)
{
//堆已經滿了,無法再插入
return;
}
heap->data[heap->size++] = value;
//對這個堆進行上浮調整
//調整的起始位置是size-1
AdjustUp(heap,heap->size - 1);
return;
}
(2)測試代碼:
void HeapPrintChar(Heap* heap,const char* msg)
{
printf("[%s]\n",msg);
size_t i = 0;
for(;i < heap->size; ++i)
{
printf("[%c | %lu] ",heap->data[i],i);
}
printf("\n");
}
void TestInsert()
{
TEST_HEADER;
Heap heap;
HeapInit(&heap,Greater);
HeapInsert(&heap,'c');
HeapInsert(&heap,'b');
HeapInsert(&heap,'a');
HeapInsert(&heap,'e');
HeapInsert(&heap,'f');
HeapInsert(&heap,'d');
HeapPrintChar(&heap,"給堆中插入6個元素");
}
(3)運行結果:
4.取堆頂元素
(1)實現代碼:
int HeapRoot(Heap* heap,HeapType* value)
{
if(heap == NULL || value == NULL)
{
//非法輸入
return 0;
}
if(heap->size == 0)
{
//空堆
return 0;
}
*value = heap->data[0];
return 1;
}
(2)測試代碼:
void TestRoot()
{
TEST_HEADER;
Heap heap;
HeapInit(&heap,Greater);
HeapInsert(&heap,'c');
HeapInsert(&heap,'b');
HeapInsert(&heap,'a');
HeapInsert(&heap,'e');
HeapInsert(&heap,'f');
HeapInsert(&heap,'d');
HeapType value;
int ret = HeapRoot(&heap,&value);
printf("ret expected 1,actual %d\n",ret);
printf("value expected f,actual %c\n",value);
}
(3)運行結果:
5.刪除堆頂的元素
思想:現將堆頂元素和堆的最後一個元素進行交換,然後刪掉最後一個元素(即此時已將堆頂元素刪除,但是可能並不滿足堆的要求),所以對堆進行下沉式調整
(1)實現代碼:
void AdjustDown(Heap* heap,size_t index)
{
if(heap == NULL)
{
return;
}
if(heap->size == 0)
{
return;
}
size_t parent = index;
size_t child = 2 * index + 1;
while(child < heap->size)
{
if(child+1 < heap->size && heap->cmp(heap->data[child+1],heap->data[child]))
{
//如果右子樹存在,並且右子樹比左子樹更符合堆的要求
//假設我們這是個小堆,就要求說
//如果右子樹比左子樹小,那麼就讓child指向右子樹
child = child + 1;
}
//child 就指向了左右子樹中更小的那個元素
if(heap->cmp(heap->data[child],heap->data[parent]))
{
Swap(&heap->data[parent],&heap->data[child]);
}
parent = child;
child = 2 * parent + 1;
}
}
void HeapErase(Heap* heap)
{
if(heap == NULL)
{
//非法輸入
return;
}
if(heap->size == 0)
{
//空堆
return;
}
//交換堆頂元素和最後一個元素
Swap(&heap->data[0],&heap->data[heap->size -1]);
//進行尾刪
--heap->size;
//從根節點出發,進行下沉調整
AdjustDown(heap,0);
return;
}
(2)測試代碼:
void TestErase()
{
TEST_HEADER;
Heap heap;
HeapInit(&heap,Greater);
HeapInsert(&heap,'c');
HeapInsert(&heap,'b');
HeapInsert(&heap,'a');
HeapInsert(&heap,'e');
HeapInsert(&heap,'f');
HeapInsert(&heap,'d');
HeapErase(&heap);
HeapPrintChar(&heap,"刪除堆頂元素");
}
(3)運行結果:
6.給定一個數組創建一個堆
(1)實現代碼:
void HeapCreate(Heap* heap,HeapType array[],size_t size)
{
if(heap == NULL)
{
return;
}
//遍歷array 數組,把數組的元素依次的插入到堆中
size_t i = 0;
for(;i < size;++i)
{
HeapInsert(heap,array[i]);
}
return;
}
(2)測試代碼:
void TestCreate()
{
TEST_HEADER;
Heap heap;
HeapInit(&heap,Greater);
HeapType array[] = {'d','e','c','a','b'};
HeapCreate(&heap,array,sizeof(array)/sizeof(array[0]));
HeapPrintChar(&heap,"創建一個堆");
}
(3)運行結果:
7.運用堆來給一個數組進行排序
思想:現將所給的數組建造成一個堆,然後再對堆進行堆頂元素的刪除
如果需要升序排序就要建一個大堆:
因爲大堆的堆頂元素爲數組中最大的值,每次刪除堆頂元素就是將當前數組中的最大值放在了數組的末尾,從而達到了升序排序
如果要進行降序排序,就要建一個小堆:
因爲小堆的堆頂元素爲數組中最小的值,每次刪除堆頂元素就是將當前數組中的最小值放在了數組的末尾,從而達到了降序排序
(1)實現代碼:
//如果要進行升序排序,那就要構建一個大堆
//如果要進行降序排序,那就要構建一個小堆
void HeapSort(HeapType array[],size_t size)
{
//把這個數組構建成一個堆
Heap heap;
HeapInit(&heap,Greater);
HeapCreate(&heap,array,size);
//循環的堆進行刪除操作
while(heap.size > 0)
{
HeapErase(&heap);
}
//循環結束後,堆排序就完成了
memcpy(array,heap.data,size * sizeof(HeapType));
return;
}
(2)測試代碼:
void TestSort()
{
TEST_HEADER;
HeapType array[] = {'d','e','c','a','b'};
HeapSort(array,sizeof(array)/sizeof(array[0]));
size_t i = 0;
for(;i < sizeof(array);++i)
{
printf("[%c] ",array[i]);
}
printf("\n");
}
(3)運行結果: