赞
踩
堆(Heap)是一种经典的树形数据结构,它是一棵完全二叉树,其每个节点的键值都不小于或不大于其左右子节点的键值,我们称之为“堆属性”。
根据堆属性,堆被分为两种类型。
大根堆(Max Heap):父节点的键值大于等于任何一个子节点的键值。
小根堆(Min Heap):父节点的键值小于等于任何一个子节点的键值。
堆的实现思路:
正如上图所示,堆的结构一般使用数组(内存连续、索引简单等)。
有了底层结构,剩下的就是功能上的实现了。
#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <stdbool.h> typedef int HPDataType; //创建堆结构体 typedef struct heap { HPDataType* a;//定义数组 int size;//定义堆大小(当前数据元素) int capacity;//定义堆容量(最大元素个数) }HP; //初始化堆 void HeapInit(HP* hp); //销毁堆 void HeapDestroy(HP* hp); //打印堆(便于测试观察) void HeapPrint(HP* hp); //交换元素 void Swap(HPDataType* p1, HPDataType* p2); //堆插入元素 void HeapPush(HP* hp, HPDataType x); //堆删除元素 void HeapPop(HP* hp); //向下调整 void AdjustDown(HPDataType* a, int n, int parent); //向上调整 void AdjustUp(HPDataType* a, int child); //判断堆是否为空 bool HeapEmpty(HP* hp); //堆大小 int HeapSize(HP* hp); //返回堆顶 HPDataType HeapTop(HP* hp);
void HeapInit(HP* hp)
{
assert(hp);
hp->a = NULL;
hp->size = hp->capacity = 0;
}
hp->a
是否置空无影响。void HeapDestroy(HP* hp)
{
assert(hp);
free(hp->a);
hp->capacity = hp->size = 0;
}
printf()
语句打印信息void HeapPrint(HP* hp)
{
assert(hp);
for (int i = 0; i < hp->size; i++)
{
printf("%d ", hp->a[i]);
}
printf("\n");
}
void HeapPush(HP* hp, HPDataType x) { assert(hp); //判断扩容 if (hp->size == hp->capacity) { // 如果堆为空则赋值capacity,堆不为空则扩充一倍 int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2; HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);//将a扩容 if (tmp == NULL) // 判断realloc是否成功 { perror("realloc fail"); exit(-1); } // 更新容量 hp->a = tmp; hp->capacity = newcapacity; } // 插入元素 hp->a[hp->size] = x; hp->size++; AdjustUp(hp->a, hp->size - 1); }
parent = (child - 1) / 2
,如果子节点<父节点 (以小堆为例,如果再if语句选用’>'则为大堆) ,则交换两节点,再将父节点设为子节点循环往复将该堆重新置为小堆//元素交换 void Swap(HPDataType* p1, HPDataType* p2) { HPDataType tmp = *p1; *p1 = *p2; *p2 = tmp; } void AdjustUp(HPDataType* a, int child) { int parent = (child - 1) / 2; //不用while(parent >= 0)//因为parent永远不为负 while (child > 0) { if (a[child] < a[parent])//a[child] > a[parent] -> 大堆 { //交换子节点父节点,并将父节点置为子节点 Swap(&a[child], &a[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } }
void HeapPop(HP* hp)
{
assert(hp);
assert(!HeapEmpty(hp));//堆不能为空
//交换元素
Swap(&hp->a[0], &hp->a[hp->size - 1]);
hp->size--;
AdjustDown(hp->a, hp->size, 0);
}
a[child] < a[parent]
则交换并且把parent变为childvoid AdjustDown(HPDataType* a, int n, int parent) { int child = parent * 2 + 1;//左子节点 while (child < n) { //确保child指向更小的孩子 if (child + 1 < n && a[child + 1] < a[child])//右孩子存在且右小于左 /* a[child + 1] > a[child]大堆 */ { child++;//将child改为右子节点 } //孩子是否小于父节点 <==> 是否应该继续交换 if (a[child] < a[parent]) //a[child] > a[parent]大堆 { Swap(&a[child], &a[parent]); parent = child; child = parent * 2 + 1; } else { break; } } }
HPDataType HeapTop(HP* hp)
{
assert(hp);
assert(hp->size > 0);
return hp->a[0];//堆顶
}
bool HeapEmpty(HP* hp)
{
assert(hp);
return hp->size == 0;//bool类型直接返回表达式hp->size == 0
}
int HeapSize(HP* hp)
{
assert(hp);
return hp->size;//直接返回size
}
堆排序通常情况如果排升序则建大堆,排降序建小堆。
下面用小堆排升序是可行的,但效率较低
//升序 - 小堆,效率低 //O(N^2) void HeapSort1(HPDataType* a, int n) { HP hp; HeapInit(&hp); // 将每个元素插入到小根堆中 for (int i = 0; i < n; i++) { HeapPush(&hp, a[i]); } //Pop 小堆的大小次数 for (int i = 0; i < n; i++) { // 每次取出小根堆的堆顶元素(最小元素),插入到数组中 a[i] = HeapTop(&hp); HeapPop(&hp); // pop函数会自动执行向下调整,保证每次取到最小值 } HeapDestroy(&hp); }
//升序 - 使用大堆,效率高 //若使用小堆则为降序 //(O*log*N) void HeapSort(int* a, int n) { //把数组变成小堆 //法一 - 通过向上调整将数组构建成小堆 /*for (int i = 1; i < n; i++) { AdjustUp(a, i); }*/ //法二 - 向下调整 int parent = (n - 1 - 1) / 2; for (int i = parent; i >= 0; i--) { AdjustDown(a, n, i); } // 不断选择堆顶元素并调整堆 for (int end = n - 1; end > 0; end--) { // 将堆顶元素(最小元素)与数组首位交换 Swap(&a[end], &a[0]); // 再次调堆(数组),选出次小数 AdjustDown(a, end, 0); } }
TopK问题即在一个n个数的序列中找到最大的前K个元素;
sizeof(int) * n
的空间,将数组a里的所有元素随机填入[0, n]范围内的数字,再手动设置k个大于n的数,最后PrintTopk
则完成测试代码。//Topk问题例子 //找n个数字中最大的前k个(令k等于10) void TestTopK() { int n = 10000; int k = 10; int* a = (int*)malloc(sizeof(int) * n); if (a == NULL) { perror("malloc fail"); exit(-1); } srand(time(0)); for (size_t i = 0; i < n; i++) { a[i] = rand() % 1000000;//随机设置n个在[0, 1000000]范围内的数字 } //设置10个大于100w的数字 a[4] = 1000000 + 4; a[1231] = 1000000 + 1; a[114] = 1000000 + 5; a[514] = 1000000 + 2; a[1919] = 1000000 + 3; a[810] = 1000000 + 8; a[571] = 1000000 + 6; a[414] = 1000000 + 7; a[2333] = 1000000 + 9; a[9999] = 1000000 + 10; PrintTopk(a, n, k); }
void PrintTopk(int* a, int n, int k) { HP hp; HeapInit(&hp); //先把前k位存在小堆中 for (int i = 0; i < k; i++) { HeapPush(&hp, a[i]); } //剩下N-K个数字与堆顶数据比较,谁大留谁 for (int i = k; i < n; i++) { if (a[i] > HeapTop(&hp)) { //法一 //将堆顶的元素删掉,把a[i]放入堆顶 //HeapPop(&hp); //HeapPush(&hp, a[i]); //把a[i]放入 hp.a[0] = a[i]; AdjustDown(hp.a, hp.size, 0); } } HeapPrint(&hp); HeapDestroy(&hp); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。