堆的定义:
- 堆是一个完全二叉树
- 堆有两种, 一种叫小堆(小根堆, 最小堆), 一种叫大堆(大根堆, 最大堆).
- 以小堆为例, 这个树的根节点是这个树中的最小的元素,对于任意一个子树来说, 子树的根节点, 小于左右孩子节点的值.
- 以大堆为例, 这个树的根节点是这个树种的最大元素,对于任意一个子树来说, 子树的根节点, 大于左右孩子节点的值.
大堆结构:
小堆结构:
用小堆来举例:
对应上图,对堆进行插入分为两步;
- 将要插入如的元素放到数组的最后
- 不断的和他的父节点进行比较,如果比父节点小,则两个节点交换内容,知道父节点的值小于插入节点的值,这和个过程称为上浮
对堆进行删除:
同样分成两部分:
- 把树顶的元素和他的孩子节点中最小的交换位置,一直吧这个根节点交换到树的最后
- 堆的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); // 堆的初始化