数据结构——堆

ops/2024/10/20 20:34:29/

目录

前言

一、堆的概念及结构

二、堆的实现

2.1 堆初始化

2.2 堆的销毁

2.3 交换数据

2.4 插入数据(插入到堆尾)

2.5 向上调整

2.6 堆的删除(删除堆顶元素)

2.7 向下调整

2.8 取堆顶

2.9 判空

完整代码

三、堆的创建

1.向上调整建堆

2.向下调整建堆

四、堆的应用

4.1 堆排序

4.2 TOP-K问题

总结


前言

之前我们了解到了一种非线性的数据结构——树,今天我们来学习二叉树的顺序结构——堆的实现,来了解堆这种数据结构


一、堆的概念及结构

如果有一个关键码的集合K = { , , ,…, },把它的所有元素按 完全二叉树 顺序存储方式存储 在一个一维 数组 中,并满足: <=且 <=( >=且 >= ) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最 大堆 或大根堆,根节点最小的堆叫做最 小堆 或小根堆。
堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵 完全二叉树

二、堆的实现

在物理结构上我们通过一个一维数组来实现堆,逻辑上堆为完全二叉树。
堆的结构:
typedef int HPDataType;typedef struct Heap {HPDataType* a;int size;//有效数据个数int capacity;//数组大小
}HP;

主要接口:

//堆初始化
void HPInit(HP* php);
//堆的插入(插入后保持为堆)
void HPPush(HP* php, HPDataType x);
//向上调整
AdjustUp(HPDataType* a, int chlid);
//堆的删除(删除堆顶元素)
void HPPop(HP* php);
//向下调整
void AdjustDown(HPDataType* a, int n, int parent);
//取堆顶
HPDataType HPTop(HP* php);
//判空
bool HPEmpty(HP* php);
//堆的销毁
void HPDestroy(HP* php);//交换数据
void Swap(HPDataType* p1, HPDataType* p2);

2.1 堆初始化

void HPInit(HP* php) {assert(php);php->a = NULL;php->size = 0;php->capacity = 0;}

和之前顺序表的初始化一样,把数组置空,大小和容量都为空。

2.2 堆的销毁

void HPDestroy(HP* php) {assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;}

释放我们开辟的数组空间,其他都置为空。

2.3 交换数据

void Swap(HPDataType* p1, HPDataType* p2) {HPDataType temp = 0;temp = *p1;*p1 = *p2;*p2 = temp;
}

定义一个交换函数,后面操作会用到。

2.4 插入数据(插入到堆尾)

插入数据后,我们还要插入后保持结构为堆。
void HPPush(HP* php, HPDataType x) {assert(php);//扩容if (php->size == php->capacity){int Newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;HPDataType* temp = (HPDataType*)realloc(php->a, Newcapacity * sizeof(HPDataType));if (temp == NULL){perror("realloc fail");return;}php->a = temp;php->capacity = Newcapacity;}php->a[php->size++] = x;//向上调整AdjustUp(php->a, php->size-1);
}

前面的插入操作和顺序表都插入一样,我们插入到堆的最后面,同时我们需要一个向上调整函数来保持结构仍为堆。

2.5 向上调整

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆
AdjustUp(HPDataType* a, int chlid) {//父节点int parent = (chlid - 1) / 2;//先上调整while (chlid > 0){if (a[chlid] < a[parent]){Swap(&a[parent], &a[chlid]);//新的父子节点chlid = parent;parent = (parent - 1) / 2;}else{break;}}}

我们从子节点找它的父节点,比较父子节点的大小,如果堆为小堆,则谁更小谁在上面,反之如果为大堆,则谁更大谁在上面。

2.6 堆的删除(删除堆顶元素)

void HPPop(HP* php) {assert(php);assert(php->size > 0);//把堆顶元素和最后一个元素交换位置Swap(&php->a[0],&php->a[php->size - 1]);php->size--;//向下调整AdjustDown(php->a,php->size,0);
}

堆的删除操作,我们先把堆顶元素和堆尾元素交换位置,最后堆的长度减一即可(可以不用删除),最后通过向下调整来保持堆的结构。

2.7 向下调整

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
void AdjustDown(HPDataType* a, int n, int parent) {int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child + 1] < a[child]){child++;}if (a[child] < a[parent]){Swap(&a[parent], &a[child]);parent = child;child = 2 * parent + 1;}else{break;}}
}

我们通过父节点来找到子节点,我们首先找到左右节点中最小的(反之如果是大堆找最大的),找到后比较子节点和父节点中最小的交换位置(反之如果是大堆找最大的)。

2.8 取堆顶

//取堆顶
HPDataType HPTop(HP* php) {assert(php);return php->a[0];
}

因为堆是通过数组实现的,所以堆顶即为数组第一个元素。

2.9 判空

bool HPEmpty(HP* php) {assert(php);return php->size == 0;
}

测试代码:

#include"Heap.h"int main() {HP hp;HPInit(&hp);//初始化堆int a[] = { 50,100,70,65,60,32 };for(int i = 0; i < sizeof(a) / sizeof(a[0]);i++){//把数组的内容依次插入到堆中HPPush(&hp, a[i]);}while (!HPEmpty(&hp)){    printf("%d\n", HPTop(&hp));HPPop(&hp);}HPDestroy(&hp);
}

完整代码

//堆初始化
void HPInit(HP* php) {assert(php);php->a = NULL;php->size = 0;php->capacity = 0;}//交换数据
void Swap(HPDataType* p1, HPDataType* p2) {HPDataType temp = 0;temp = *p1;*p1 = *p2;*p2 = temp;
}//向上调整
AdjustUp(HPDataType* a, int chlid) {//父节点int parent = (chlid - 1) / 2;//先上调整while (chlid > 0){if (a[chlid] < a[parent]){Swap(&a[parent], &a[chlid]);//新的父子节点chlid = parent;parent = (parent - 1) / 2;}else{break;}}}//堆的插入(插入后保持为堆)
void HPPush(HP* php, HPDataType x) {assert(php);//扩容if (php->size == php->capacity){int Newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;HPDataType* temp = (HPDataType*)realloc(php->a, Newcapacity * sizeof(HPDataType));if (temp == NULL){perror("realloc fail");return;}php->a = temp;php->capacity = Newcapacity;}php->a[php->size++] = x;//向上调整AdjustUp(php->a, php->size-1);
}//向下调整
void AdjustDown(HPDataType* a, int n, int parent) {int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child + 1] < a[child]){child++;}if (a[child] < a[parent]){Swap(&a[parent], &a[child]);parent = child;child = 2 * parent + 1;}else{break;}}
}//堆的删除(删除堆顶元素)
void HPPop(HP* php) {assert(php);assert(php->size > 0);//把堆顶元素和最后一个元素交换位置Swap(&php->a[0],&php->a[php->size - 1]);php->size--;//向下调整AdjustDown(php->a,php->size,0);
}
//取堆顶
HPDataType HPTop(HP* php) {assert(php);return php->a[0];
}
//判空
bool HPEmpty(HP* php) {assert(php);return php->size == 0;
}
//堆的销毁
void HPDestroy(HP* php) {assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;}

三、堆的创建

前面我们通过数组插入堆来建堆,我们还可以通过其他方法来创建堆

int main() {HP hp;int a[] = { 50,100,70,65,60,32 };HPInitArray(&hp,a,sizeof(a) / sizeof(a[0]));
}

1.向上调整建堆

void HPInitArray(HP* php, HPDataType* a, int n) {//为堆开辟空间php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);if (php->a == NULL){perror("malloc fail");return;}//拷贝数组的值到堆中memcpy(php->a, a, sizeof(HPDataType) * n);php->size = php->capacity = n;//向上调整建堆  O(N*longN)for (int i = 1; i < php->size; i++){AdjustUp(php->a, i);}
}

我们从堆的第二个节点开始先上开始调整的。

向上调整建堆的时间复杂度为:O(N*longN)

2.向下调整建堆

void HPInitArray(HP* php, HPDataType* a, int n) {php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);if (php->a == NULL){perror("malloc fail");return;}//拷贝数组的值到堆中memcpy(php->a, a, sizeof(HPDataType) * n);php->size = php->capacity = n;//向下调整建堆 O(N)for (int i = (php->size - 1 - 1) / 2; i >= 0; i--){AdjustDown(php->a, php->size, i);}}

我们先下调整的初始位置为最后一个子节点的父节点开始向下调整的。

向下调整的时间复杂度为:O(N)

所以我们通常使用向下调整建堆,时间复杂度小。

四、堆的应用

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

4.1 堆排序

1. 建堆
升序:建大堆
降序:建小堆
2. 利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
#include"Heap.h"void HeapSort(HPDataType* a, int n) {//向下调整建堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}//小堆降序  大堆升序int end = n - 1;while (end > 0){Swap(&a[0],&a[end]);AdjustDown(a, end, 0);end--;}}int main() {int a[] = { 20,17,5,3,4,16 };HeapSort(a, sizeof(a) / sizeof(a[0]));}
升序:建大堆
降序:建小堆

4.2 TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能
数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
我们先在文件中创建出数据:
void CreateNdate()
{int n = 1000;//个数srand(time(0));FILE* fin = fopen("date.txt", "w");if (fin == NULL){perror("fopen fail");return;}int x = 0;for (int i = 0; i < n; i++){x = (rand() + i) % 10000000;fprintf(fin, "%d\n",x);}fclose(fin);
}

然后通过建堆比较来使得堆里元素为最大(或最小)

void tok() {int k = 0;printf("请输入k的值>");scanf("%d", &k);FILE* fout = fopen("date.txt", "r");if (fout == NULL){perror("fopen error");return;}int* minheap = (int*)malloc(sizeof(int) * k);for (int i = 0; i < k; i++){fscanf(fout, "%d", &minheap[i]);}//建一个k的小堆for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDown(minheap, k, i);}int x = 0;//比较while (fscanf(fout, "%d", &x) != EOF){if (x > minheap[0]){minheap[0] = x;AdjustDown(minheap, k, 0);}}for (int i = 0; i < k; i++){printf("%d ", minheap[i]);}fclose(fout);
}

比如我们求1000个数中前10个最大的:


总结

以上我们讲了二叉树的顺序结构——堆,讲了堆的实现,和堆的应用。希望对你有所帮助。


http://www.ppmy.cn/ops/5628.html

相关文章

Matlab|含sop的配电网重构(含风光|可多时段拓展)

目录 1 主要内容 2 部分程序 3 下载链接 1 主要内容 之前分享了很多配电网重构的程序&#xff0c;每个程序针对场景限定性比较大&#xff0c;程序初学者修改起来难度较大&#xff0c;本次分享一个基础程序&#xff0c;针对含sop的配电网重构模型&#xff0c;含风电和光伏&am…

部署轻量级Gitea替代GitLab进行版本控制(一)

Gitea 是一款使用 Golang 编写的可自运营的代码管理工具。 Gitea Official Website gitea: Gitea的首要目标是创建一个极易安装&#xff0c;运行非常快速&#xff0c;安装和使用体验良好的自建 Git 服务。我们采用Go作为后端语言&#xff0c;这使我们只要生成一个可执行程序即…

git切换源失败解决方案

git切换源失败解决方案 git切换源git切换源失败(无效) git切换源 git可以使用命令行切换源&#xff0c;一般使用的源有两个地址&#xff0c;git原生地址和淘宝镜像地址&#xff0c;部分公司会使用内部地址。 源切换后&#xff0c;npm i就是从源地址拉取相关依赖了。 原生地址…

2024上海国际半导体制造设备材料与核心部件展览会

2024上海国际半导体制造设备材料与核心部件展览会 2024 Shanghai International Semiconductor Manufacturing Equipment Materials and Core Components Exhibition 时间&#xff1a;2024年11月18日-20日 地点&#xff1a;上海新国际博览中心 详询主办方陆先生 I38&#…

智慧浪潮下的产业园区:解读智慧化转型如何打造高效、绿色、安全的新产业高地

随着信息技术的飞速发展&#xff0c;智慧化转型已经成为产业园区发展的重要趋势。在智慧浪潮的推动下&#xff0c;产业园区通过集成应用物联网、大数据、云计算、人工智能等先进技术手段&#xff0c;实现园区的智慧化、高效化、绿色化和安全化&#xff0c;从而打造成为新产业高…

CSS3 新特性 box-shadow 阴影效果、圆角border-radius

圆角 使用CSS3 border-radius属性&#xff0c;你可以给任何元素制作"圆角"&#xff0c;border-radius属性&#xff0c;可以使用以下规则&#xff1a; &#xff08;1&#xff09;四个值&#xff1a;第一个值为左上角&#xff0c;第二个值为右上角&#xff0c;第三个值…

Mac下删除旧版本.net sdk

参照微软官网给的方法&#xff0c;Releases dotnet/cli-lab (github.com) 好像不能直接的解决问题&#xff0c;我做一下补充&#xff0c;希望对需要删除旧版本sdk的小伙伴们有所帮助 1:下载工具包 Releases dotnet/cli-lab (github.com) 2:打开终端&#xff0c;cd切换到该…

免费ssl通配符证书申请教程

在互联网安全日益受到重视的今天&#xff0c;启用HTTPS已经成为网站运营的基本要求。它不仅保障用户数据传输的安全&#xff0c;提升搜索引擎排名&#xff0c;还能增强用户对网站的信任。通配符证书是一种SSL/TLS证书&#xff0c;用于同时保护一个域名及其所有下一级子域名的安…