在实现堆的操作之前,先来明白什么是堆?
- 堆中某个节点的值总是不大于或不小于其父节点的值
- 堆总是一棵完全二叉树。
要说到完全二叉树,直观的判断就是,层序遍历一棵树,如果树的某个节点没有右子树或者是没有子树,那么从这个节点后面的所有节点都不能够有任何子树。
接下来我们说说堆。堆分为大堆跟小堆。大堆的意思就是每一个节点都是它这个节点树的最大值。小堆同理是最小值。
基本操作与实现
基本操作
//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;
}
欢迎大家共同讨论,如有错误及时联系作者指出,并改正。谢谢大家!