堆的定义:
- 堆是一个完全二叉树
- 堆有两种, 一种叫小堆(小根堆, 最小堆), 一种叫大堆(大根堆, 最大堆).
- 以小堆为例, 这个树的根节点是这个树中的最小的元素,对于任意一个子树来说, 子树的根节点, 小于左右孩子节点的值.
- 以大堆为例, 这个树的根节点是这个树种的最大元素,对于任意一个子树来说, 子树的根节点, 大于左右孩子节点的值.
大堆结构:
小堆结构:
用小堆来举例:
对应上图,对堆进行插入分为两步;
- 将要插入如的元素放到数组的最后
- 不断的和他的父节点进行比较,如果比父节点小,则两个节点交换内容,知道父节点的值小于插入节点的值,这和个过程称为上浮
对堆进行删除:
同样分成两部分:
- 把树顶的元素和他的孩子节点中最小的交换位置,一直吧这个根节点交换到树的最后
- 堆的size--
堆排序:
堆排序就是把一个数组中的所有元素都一一插入一个堆,然后逐个删除,删除所有的元素之后,这是所有的元素已经有序,就打到了排序的目的
- 从大到小排序需要一个小堆
- 从小到大排序需要一个大堆
头文件 heap.h
#pragma once
#include<unistd.h>
#include<stdio.h>
#define HeapMaxSize 1000 //堆结构体中数组的最大容量
typedef int HeapType; //定义堆元素的类型
typedef int (*Compare)(HeapType* a, HeapType* b); //返回值为int 参数为两个对类型指针的函数指针类型
typedef struct Heap {
HeapType data[HeapMaxSize];
size_t size; //对中已有的元素个数
Compare cmp; //由这个函数指针决定这个堆是大堆还是小堆
}Heap;
//这里的堆由一个数组来代替,父子节点之间的关系由数组下标之间的关系来维持
//比如一个几点的下标为x, 那么他的做孩子节点的下标就是2*x+1,右孩子节点的小标是2*x+2,父节点的下标是(x-1)/2
//这样做的好处就是省去了构建真正的树的麻烦操作
void HeapInit(Heap* heap, Compare compare); // 堆的初始化 void HeapInsert(Heap* heap, HeapType value); //向堆中插入元素 int HeapRoot(Heap* heap, HeapType* value); //取堆顶元素 void HeapErase(Heap* heap); // 删除堆顶元素 int HeapEmpty(Heap* heap); //判断堆书否为空 size_t HeapSize(Heap* heap); //获得堆的size void HeapDestroy(Heap* heap); //删除堆 // 在我们不想开辟额外的空间, 或者消耗额外的时间的前提下, // 如果我们想进行从小到大排序, 就需要一个大堆 // 如果我们想进行从大到小排序, 就需要一个小堆 void HeapSort(HeapType array[], size_t size);
头文件的具体实现 heap.c
#include "heap.h"
int MaxHeapCmp(HeapType* a, HeapType* b) {
if(*a > *b) {
return 1;
} else {
return 0;
}
}
int MinHeapCmp(HeapType* a, HeapType* b) {
if(*a < *b) {
return 1;
} else {
return 0;
}
}
void Swap(HeapType* a , HeapType* b) {
HeapType x = *a;
*a = *b;
*b = x;
}
void HeapInit(Heap* heap, Compare compare) {
if(heap == NULL) {
//非法输入
return;
}
heap->size = 0;
heap->cmp = compare;
}
void HeapInsert(Heap* heap, HeapType value) {
if(heap == NULL) {
//非法输入
return;
}
if(heap->size >= HeapMaxSize) {
//堆满
return;
}
heap->data[heap->size++] = value;
int cur = heap->size - 1;
while(1) {
if(!heap->cmp(&heap->data[cur],&heap->data[(cur -1)/2])){
break;
}
if(heap->cmp(&heap->data[cur], &heap->data[(cur-1)/2])) {
Swap(&heap->data[cur], &heap->data[(cur-1)/2]);
cur = (cur - 1) / 2;
}
}
}
int HeapRoop(Heap* heap, HeapType* value) {
if(heap == NULL) {
return 0;
}
if(heap->size == 0) {
return 0;
}
*value = heap->data[0];
return 1;
}
void heapDown(Heap* heap) {
size_t cur = 0; //标记当前要下沉的结点
size_t lchild = 2*cur + 1;
size_t rchild = 2*cur + 2;
heap->size--;
while(1){
if(lchild < heap->size && rchild < heap->size) {
//两个子树都在树的范围之内,选择最小的一个交换
int exc;
//exc用来保存满足条件的子节点的坐标
//当这个堆是一个小堆时,exc保存左右子节点较小值的下标
//当这个堆是一个大堆是,exc保存左右子节点较大值的下标
exc = heap->cmp(&heap->data[lchild], &heap->data[rchild]) ? lchild : rchild;
Swap(&heap->data[cur], &heap->data[exc]);
cur = exc;
lchild = 2*cur + 1;
rchild = 2*cur + 2;
} else if(lchild < heap->size) {
//如果走到这里说明右子树超出树的范围,判断左子树是否满足条件
// a) 满足交换
// b) 不满足退出循环
if(heap->cmp(&heap->data[lchild],&heap->data[cur])) {
Swap(&heap->data[cur], &heap->data[lchild]);
}
// 这个cur结点的特点就是只有左子树,不管交不交换,最后都直接退出程序
return;
} else {
//如果走到这里说明左右子树不在树范围之内,说明已经到了树的最低层
//直接返回
return;
}
}
}
void HeapErase(Heap* heap) {
if(heap == NULL) {
return;
}
if(heap->size == 0) {
return;
}
Swap(&heap->data[0], &heap->data[heap->size - 1]);
heapDown(heap);
}
int HeapEmpty(Heap* heap) {
if(heap == NULL) {
return 0;
}
if(heap->size == 0) {
return 0;
} else {
return 1;
}
}
size_t HeapSize(Heap* heap) {
if(heap == NULL) {
return 0;
}
return heap->size;
}
void HeapDestroy(Heap* heap) {
if(heap == NULL) {
return;
}
heap->size = 0;
heap->cmp = NULL;
}
void HeapSort(HeapType array[], size_t size) {
if(array == NULL) {
return;
}
if(size == 0) {
return;
}
Heap heap;
//这里的初始化很关键
//如果要从小到大排序,就需要一个大堆
//若果要从大到小排序,就需要一个小堆
HeapInit(&heap, MinHeapCmp); //这里演示小堆,即从大到小排序
size_t i = 0;
for(; i<size; i++) {
HeapInsert(&heap, array[i]);
}//循环完了之后i的值就是将数字插入完成之后堆的size的值
while(heap.size > 0) {
HeapErase(&heap);
}
//删除完毕之后,此时heap中的元素[0, i) 已经有序
//复制给array即可
size_t j = 0;
for(; j<i; j++) {
array[j] = heap.data[j];
}
}
# if 1
///
// 以下为测试代码
///
#define FUNHEAD printf("\n==============================%s=========================\n", __FUNCTION__)
void Print(Heap* heap) {
if(heap == NULL) {
return;
}
size_t i;
for(i=0; i<heap->size; i++) {
printf("%d ",heap->data[i]);
}
printf("\n");
}
void TestInit() {
FUNHEAD;
Heap heap;
HeapInit(&heap, NULL);
printf("heap.size expected 0, actual %lu\n", heap.size);
printf("heap.cmp expected NULL, actauall %p\n", heap.cmp);
}
void TestInsert() {
FUNHEAD;
Heap heap;
HeapInit(&heap, MinHeapCmp);
HeapInsert(&heap, 9);
HeapInsert(&heap, 8);
HeapInsert(&heap, 7);
HeapInsert(&heap, 6);
HeapInsert(&heap, 5);
HeapInsert(&heap, 4);
HeapInsert(&heap, 3);
Print(&heap);
}
void TestErase() {
FUNHEAD;
Heap heap;
HeapInit(&heap, MinHeapCmp);
HeapInsert(&heap, 9);
HeapInsert(&heap, 8);
HeapInsert(&heap, 7);
HeapInsert(&heap, 6);
HeapInsert(&heap, 5);
HeapInsert(&heap, 4);
HeapInsert(&heap, 3);
size_t i = heap.size;
while(heap.size != 0) {
HeapErase(&heap);
Print(&heap);
}
size_t j = 0;
for(; j<i; j++) {
printf("%d ",heap.data[j]);
}
printf("\n");
}
void TestSort() {
FUNHEAD;
int array[] = {1,6,9,4,8,5,77,34,76,22,1,6,0};
size_t size = sizeof(array) / sizeof(array[0]);
HeapSort(array, size);
size_t i = 0;
for(; i<size; i++) {
printf("%d ",array[i]);
}
printf("\n");
}
int main() {
TestInit();
TestInsert();
TestErase();
TestSort();
}
#endif
实验结果:
void HeapInit(Heap* heap, Compare compare); // 堆的初始化