資料結構之堆的基本操作
在實現堆的操作之前,先來明白什麼是堆?
- 堆中某個節點的值總是不大於或不小於其父節點的值
- 堆總是一棵完全二叉樹。
要說到完全二叉樹,直觀的判斷就是,層序遍歷一棵樹,如果樹的某個節點沒有右子樹或者是沒有子樹,那麼從這個節點後面的所有節點都不能夠有任何子樹。
接下來我們說說堆。堆分為大堆跟小堆。大堆的意思就是每一個節點都是它這個節點樹的最大值。小堆同理是最小值。
基本操作與實現
基本操作
//heap.h
#pragma once
#include <stdio.h>
#include <stddef.h>
#define HEAPMAX 1000
typedef char HeapType;
typedef int(*Compare)(int a, int b);//函式指標,來確定將來的堆是最大堆還是最小堆
typedef struct Heap{
HeapType data[HEAPMAX];
size_t size;
Compare cmp;
}Heap;
void HeapInit(Heap* heap, Compare cmp);//初始化堆
void HeapInsert(Heap* heap, HeapType to_insert);//在堆中插入
int HeapRoot(Heap* heap, HeapType* root);//取堆頂元素
void HeapErase(Heap* heap);//刪除堆頂元素
void HeapDestroy(Heap* heap);//銷燬堆
void HeapCreat(Heap* heap, HeapType array[], size_t size);//根據一個數組建立堆
void HeapSort(HeapType array[], size_t size);//堆排
實現
其實堆的操作都很簡單,但是最核心的兩個就是插入與刪除。這兩個我們單獨來說。
#include "heap.h"
#define HEAD printf("===================%s====================\n",__FUNCTION__);
void HeapPrintChar(Heap* heap)
{
if(heap == NULL) {
return;
}
size_t i = 0;
for(; i < heap->size; ++i) {
printf("%c ", heap->data[i]);
}
printf("\n");
}
int Greater(int a, int b)
{
return a > b ? 1 : 0;
}
int Less(int a, int b)
{
return a < b ? 1 : 0;
}
void HeapInit(Heap* heap, Compare cmp)//初始化堆
{
if(heap == NULL) {
return;
}
heap->size = 0;
heap->cmp = cmp;
return;
}
int HeapRoot(Heap* heap, HeapType* root)//取堆頂元素
{
if(heap == NULL) {
return 0;
}
if(heap->size == 0) {
return 0;
}
*root = heap->data[0];
return 1;
}
void HeapDestroy(Heap* heap)//銷燬堆
{
if(heap == NULL) {
return;
}
heap->size = 0;
heap->cmp = NULL;
return;
}
void HeapCreat(Heap* heap, HeapType array[], size_t size)//根據一個數組建立堆
{
if(heap == NULL || array == NULL || size == 0) {
return;
}
size_t i = 0;
for(; i < size; ++i) {
HeapInsert(heap, array[i]);
}
return;
}
插入
我們在實現堆的時,利用一個順序表來實現。由於是層序遍歷,且堆是完全二叉樹。所以順序表完全能夠處理好堆的基本操作。再者,定義一個函式指標,這個函式指標可以確定我們的堆是大堆還是小堆。接著就是插入了,由於要考慮到插入完畢後還是堆,那麼就要考慮到堆的性質,首先是完全二叉樹,接著每個節點都是它節點子樹的最大值或最小值。插入很簡單,我們只需要在順序表尾部插入即可。那如何保證堆的第二條性質呢?
所以在插入的時候,要對插入的元素與它的父節點進行比較。而如何確定它的父節點呢?這很簡單,由於是層序遍歷在順序表內。那麼一個節點的父節點的下標就是它自己的下標減一除二。即parent = (child - 1)/2
//實現
void Swap(HeapType* a, HeapType* b)
{
HeapType tmp = *a;
*a = *b;
*b = tmp;
}
void HeapInsert(Heap* heap, HeapType to_insert)//在堆中插入
{
if(heap == NULL) {
return;
}
if(heap->size >= HEAPMAX) {
return;
}
heap->data[heap->size] = to_insert;
++heap->size;
size_t child = heap->size - 1;
size_t parent = (child - 1) / 2;//父節點
while(child >= 0 && child < heap->size &&
parent >= 0 && parent < heap->size) {
if(heap->data[child] > heap->data[parent]){//判斷是否需要交換
Swap(&heap->data[child], &heap->data[parent]);
child = parent;//交換完畢後,要上浮,此時子節點變成之前的父節點,以此類推
parent = (child - 1) / 2;
} else {
break;
}
}
return;
}
刪除
在實現完插入之後,接下來就是刪除。同樣,刪除也得滿足刪除後的樹仍舊是一個堆。且滿足堆的基本條件。這裡,我們的思路採用的是刪除堆頂。具體如下:
這裡,我們在刪除之前,先將堆頂元素與最後一個元素交換,然後刪除最後一個元素,也就是結構體內的size減一。刪除完畢後,開始調整。調整的過程就是下沉。此時堆頂是6,先將節點元素與其左右子樹分別比較。將左右子樹中較小的與節點交換。如果沒有右子樹,那麼直接與左子樹交換。一次交換完畢後,此時新節點等於交換的子樹。以此類推。這就是下沉。
//實現
void HeapErase(Heap* heap)//刪除堆頂元素
{
if(heap == NULL) {
return;
}
if(heap->size == 0) {
return;
}
Swap(&heap->data[heap->size - 1], &heap->data[0]);
--heap->size;
size_t parent = 0;
size_t child = 2*parent + 1;//交換並刪除
while(child >= 0 && child< heap->size &&
parent >= 0 && parent < heap->size) {
if((child + 1) < heap->size){//判斷是否有右子樹,並且取出其中較小的
if(!heap->cmp(heap->data[child], heap->data[child+1])) {
child = child + 1;
}
}
if(heap->data[child] > heap->data[parent]) {//判斷是否需要下沉
Swap(&heap->data[child], &heap->data[parent]);
parent = child;
child = 2*parent + 1;
} else {
break;
}
}
printf("\n");
return;
}
堆排
在實現完堆的基本操作以後,我們可以考慮一下堆排序的實現。我們其實可以發現,在刪除堆內元素的時候每次刪除的都是最大的一個。並且最大或最小的一個都沉底了。
我們發現,每次刪除一次,後面的元素都是一個有序的。而刪除到最後,整個順序表就是一個有序的順序表。如果這時候將size放到最後,那麼此時恰好就是一個有序的陣列。
//實現
void HeapSort(HeapType array[], size_t size)//堆排
{
if(array == NULL || size == 0) {
return;
}
Heap heap;
HeapInit(&heap, Greater);
HeapCreat(&heap, array, size);//先將陣列插入堆中
size_t i = 0;
for(; i < size; ++i) {
HeapErase(&heap);//刪除堆內所有元素
}
heap.size = size;
i = 0;
for(; i < size; ++i) {
array[i] = heap.data[i];//把堆內元素複製到陣列中,完成排序
}
return;
}
測試程式碼如下
void TestInsert()
{
HEAD;
Heap heap;
HeapInit(&heap, Greater);
HeapInsert(&heap, 'b');
HeapInsert(&heap, 'z');
HeapInsert(&heap, 'a');
HeapInsert(&heap, 'f');
HeapInsert(&heap, 'd');
HeapInsert(&heap, 'c');
HeapInsert(&heap, 'n');
HeapPrintChar(&heap);
}
void TestRoot()
{
HEAD;
Heap heap;
HeapInit(&heap, Greater);
HeapInsert(&heap, 'b');
HeapInsert(&heap, 'z');
HeapInsert(&heap, 'a');
HeapInsert(&heap, 'f');
HeapInsert(&heap, 'd');
HeapInsert(&heap, 'c');
HeapInsert(&heap, 'n');
HeapType root;
int ret = HeapRoot(&heap, &root);
printf("ret expected 1, ret actual %d\n",ret);
printf("root expected z, root actual %c\n",root);
}
void TestErase()
{
HEAD;
Heap heap;
HeapInit(&heap, Greater);
HeapInsert(&heap, 'b');
HeapInsert(&heap, 'z');
HeapInsert(&heap, 'a');
HeapInsert(&heap, 'f');
HeapInsert(&heap, 'd');
HeapInsert(&heap, 'c');
HeapInsert(&heap, 'n');
HeapPrintChar(&heap);
printf("\n");
HeapErase(&heap);
HeapPrintChar(&heap);
HeapErase(&heap);
HeapPrintChar(&heap);
HeapErase(&heap);
HeapPrintChar(&heap);
}
void TestSort()
{
HEAD;
HeapType array[] = {'c', 'd', 'a', 'f', 'z', 'e'};
size_t size = sizeof(array)/sizeof(array[0]);
HeapSort(array, size);
size_t i = 0;
for(; i < size; ++i) {
printf("%c ", array[i]);
}
printf("\n");
}
void TestCreat()
{
HEAD;
Heap heap;
HeapInit(&heap, Greater);
HeapType array[] = {'c', 'd', 'a', 'f', 'z', 'e'};
size_t size = sizeof(array)/sizeof(array[0]);
HeapCreat(&heap, array, size);
HeapPrintChar(&heap);
}
int main()
{
TestInsert();
TestRoot();
TestErase();
TestCreat();
TestSort();
printf("\n");
printf("\n");
printf("\n");
printf("\n");
printf("\n");
return 0;
}
歡迎大家共同討論,如有錯誤及時聯絡作者指出,並改正。謝謝大家!