【数据结构】堆、堆排序(包你学会的)

文章目录

  • 前言
  • 堆(Heap)
    • 1、堆的概念及结构
    • 2、堆的分类
      • 2.1、小堆的结构
      • 2.2、大堆的结构
      • 2.3、找到规律并证明
    • 3、堆的实现(小堆)
      • 3.1、堆的结构以及接口
      • 3.2、初始化、销毁
      • 3.3、交换父子结点(后续需要)
      • 3.4、插入
      • 3.5、删除
      • 3.6、堆顶
      • 3.7、获取堆的有效元素个数
      • 3.8、判空
      • 总代码
        • Heap.h源文件
        • Heap.c源文件
        • Test.c源文件(头文件)
  • 堆排序(HeapSort)——升序


前言

堆(Heap)

1、堆的概念及结构

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

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

2、堆的分类

大顶堆:每个节点的值都大于或者等于它的左右子节点的值。
小顶堆:每个节点的值都小于或者等于它的左右子节点的值。

2.1、小堆的结构

由下图我们可以看出,这是个小堆,因为每一个父亲结点都比其子结点小,例如:父亲12小于子结点17和58,父亲结点17又小于它的子结点27和32,58也小于72,所以它是个小堆
而树形图右侧则是它在数组里面的存储结构。
在这里插入图片描述

2.2、大堆的结构

理解了小堆,大堆同理,只不过和小堆相反,由下图所示 此时的每一个父亲结点都大于其子结点,
在这里插入图片描述

2.3、找到规律并证明

既然堆的逻辑结构是完全二叉树,那么它就同样具有完全二叉树的性质。

对于完全二叉树,若从上至下、从左至右编号,以根节点为0开始,则编号为i的节点,其左孩子结点编号为2i+1,右孩子节点编号为2i+2,父亲节点为 (i-1) / 2。

我们以小堆为例证明一下!

在这里插入图片描述

3、堆的实现(小堆)

3.1、堆的结构以及接口

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
#include<time.h>typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;
//初始化
void HeapInit(HP* php);
//销毁
void HeapDestory(HP* php);
//交换父子结点
void Swap(HPDataType* p1, HPDataType* p2);
//插入
void HeapPush(HP*php,HPDataType x);
//删除
void HeapPop(HP* php);
//堆顶
HPDataType HeapTop(HP* php);
//有效元素个数
size_t HeapSize(HP* php);
//判空
bool HeapEmpty(HP* php);

3.2、初始化、销毁

初始化和销毁我相信各位大佬都没问题,这个我就不再解释了哈

//初始化
void HeapInit(HP* php)
{assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}
//销毁
void HeapDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}

3.3、交换父子结点(后续需要)

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

3.4、插入

插入操作就是先将元素插到堆的末尾,即最后一个孩子的后面,插入之后如果堆的性质发生破坏,将新插入结点顺着其双亲网上调整到合适位置即可。
如下图所示:10就是我们新插入的元素,插入时它在最后一个元素的后面,这时候10为孩子,我们可以根据孩子的下标通过公式parent = (child - 1) / 2可以找到父亲的下标,父亲元素为28,已知我们要建小堆,即孩子>父亲,而现在堆的性质已经受到破坏了,所以我们就要进行“向上调整”。过程如下图:
在这里插入图片描述
代码:

//向上调整
void AdjustUp(HPDataType* a, int child)
{//根据孩子找到父亲结点int parent = (child - 1) / 2;//while (parent >= 0)while (child > 0){//因为是小堆,所以父亲比孩子大就交换if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;//child = (child - 1) / 2;//parent = (parent - 1) / 2;}else{break;}}
}// O(logN)
void HeapPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail");exit(-1);}php->a = tmp;php->capacity = newCapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);
}

3.5、删除

这里可不敢和顺序表链表搞杂了,堆的删除就是删除堆顶元素
步骤:

  • 首先把第一个数据和最后一个数据进行交换
  • 减减size
  • 向下调整

过程如下图所示:
在这里插入图片描述
代码:

//AdjustDown就是向下调整
void AdjustDown(int* a, int size, int parent)
{//根据父亲找到孩子(左孩子)int child = parent * 2 + 1;while (child < size){// 假设左孩子小,如果解设错了,更新一下if (child+1 < size && a[child + 1] < a[child]){++child;}//如果父亲大于孩子就交换if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapPop(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);
}

3.6、堆顶

这个实现起来就很简单了,直接返回堆顶就好啦,记得断言一下哦~
代码:

HPDataType HeapTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}

3.7、获取堆的有效元素个数

也是一样简单,直接返回size就可以啦,也没什么讲的,断言~
直接上代码:

size_t HeapSize(HP* php)
{assert(php);return php->size;
}

3.8、判空

这个就和上一节的栈的判空一模一样啦,断言然后直接若size为0就返回
直接上代码:

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

总代码

Heap.h源文件
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
#include<time.h>typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;void HeapInit(HP* php);
void HeapDestory(HP* php);
void Swap(HPDataType* p1, HPDataType* p2);
void HeapPush(HP*php,HPDataType x); 
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
size_t HeapSize(HP* php);
bool HeapEmpty(HP* php);
Heap.c源文件
#include"Heap.h"// 小堆
void HeapInit(HP* php)
{assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}void HeapDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}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)while (child > 0){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;//child = (child - 1) / 2;//parent = (parent - 1) / 2;}else{break;}}
}// O(logN)
void HeapPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail");exit(-1);}php->a = tmp;php->capacity = newCapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);
}void AdjustDown(int* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){// 假设左孩子小,如果解设错了,更新一下if (child+1 < size && a[child + 1] < a[child]){++child;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapPop(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 HeapTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}size_t HeapSize(HP* php)
{assert(php);return php->size;
}bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}
Test.c源文件(头文件)
#include"Heap.h"int main()
{	int a[] = {4,6,2,1,5,8,2,9};int n=sizeof(a) / sizeof(int);HP hp;HeapInit(&hp);for (int i = 0; i < n;i++){HeapPush(&hp, a[i]);}
//堆顶前k个元素
// 	int k = 3;
// 	while (k--)
// 	{
// 		printf("%d\n", HeapTop(&hp));
// 		HeapPop(&hp);
// 	}
// }while (!HeapEmpty(&hp)){printf("%d ", HeapTop(&hp));HeapPop(&hp);}printf("\n");return 0;
}

堆排序(HeapSort)——升序

步骤:

  • 先将待排序的数组构成大堆,这个时候堆顶的元素就是整个堆里最大的元素
  • 然后将堆顶元素和末尾元素进行交换,现在最大元素就是末尾那个结点了,调整排序的时候不把它算进去,此时待排序元素个数为n-1。
  • 把其他的待排元素构成大堆,再回到第一步,交换首尾元素,然后待排元素个数再-1…如此循环

注意:

  • 升序建大堆
  • 降序建小堆

步骤一:向下调整算法建大堆
思路图:
在这里插入图片描述
步骤二:排序
思路图:
在这里插入图片描述
结论:

已知建堆的时间复杂度为O(N),而在排序过程中,由于要每次选出剩余数中最小的数,并保存到每次最后的节点,并要再执行一次向下调整算法,总共需要进行N-1(≈N)次,则向下调整算法的时间复杂度:O(NlogN),再加上建堆的时间复杂度,则=N*logN+N,综上,堆排序的时间复杂度:O(NlogN)

代码:

//升序
void HeapSort(int* a, int n)
{//建大堆// for(int i=1; i<n; i++)// {// 	AdjustUp(a,i);// }   for (int i = (n-1-1)/2; i >= 0; --i){AdjustDown(a, n, i);}//n是数组最后一个元素下标的下一个int end=n-1;while(end>0){Swap(&a[0],&a[end]);//需要注意的是 每次交换完之后关系就全乱了 所以需要再次向下调整AdjustDown(a,end,0);--end;}
}int main()
{int a[] = { 4, 6, 2, 1, 5, 8, 2, 9 };HeapSort(a, sizeof(a)/sizeof(int));for (int i = 0; i < sizeof(a)/sizeof(int); i++){printf("%d ", a[i]);}printf("\n");return 0;
}

测试结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/578930.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营第二十四天| 理论基础,77. 组合

题目与题解 参考资料&#xff1a;回溯法理论基础 带你学透回溯算法&#xff08;理论篇&#xff09;| 回溯法精讲&#xff01;_哔哩哔哩_bilibili 77. 组合 题目链接&#xff1a;​​​​​​​​​​​​​​77. 组合 代码随想录题解&#xff1a;77. 组合 视频讲解&#xff…

c语言中的联合体和枚举

这篇文章总结一下c语言中的联合体和枚举。看看这两个东西到底是什么。大家一起学习。 文章目录 一、联合体1.联合体类型的声明。2.联合体的大小。3.相同成员的结构体和联合体对比4.联合体大小的计算。 二、枚举类型1.枚举类型的声明。2.枚举类型的优点。枚举类型的使用。 一、联…

C++王牌结构hash:哈希表开散列(哈希桶)的实现与应用

目录 一、开散列的概念 1.1开散列与闭散列比较 二、开散列/哈希桶的实现 2.1开散列实现 哈希函数的模板构造 哈希表节点构造 开散列增容 插入数据 2.2代码实现 一、开散列的概念 开散列法又叫链地址法(开链法)&#xff0c;首先对关键码集合用散列函数计算散列地址&…

一文教你如何轻松领取腾讯云优惠券

腾讯云作为国内领先的云计算服务商&#xff0c;为用户提供了丰富的云产品和服务。为了让更多用户享受到腾讯云服务的优质体验&#xff0c;腾讯云推出了各种优惠券&#xff0c;让用户在购买云服务时能够获得更多实惠。本文将为大家详细介绍如何轻松领取腾讯云优惠券&#xff0c;…

智慧公厕,为智慧城市建设注入了新的活力

随着智慧城市的快速发展&#xff0c;公共厕所不再是简单的功能设施&#xff0c;而是成为了提升城市形象、改善民生服务的重要一环。智慧公厕作为新形态的公共厕所&#xff0c;通过精准监测公厕内部的人体活动状态、人体存在状态、空气质量情况、环境变化情况、设施设备运行状态…

使用PopLDdecay软件绘制LD衰减图

前记 PopLDdecay是一款用于进行种群遗传学和关联分析的软件。它可以在全基因组水平上进行基因型数据的相关性和衰减分析&#xff0c;帮助研究人员探索种群间的遗传差异和突变选择的模式。 使用PopLDdecay可以实现以下功能&#xff1a; 遗传距离的计算&#xff1a;可以计算遗…

bugku-web-eval

页面源码 <code><span style"color: #000000"> <span style"color: #0000BB"><?php <br /> </span><span style"color: #007700">include </span><span style"color: #DD0000"&…

37-巩固练习(一)

37-1 if语句等 1、问&#xff1a;输出结果 int main() {int i 0;for (i 0; i < 10; i){if (i 5){printf("%d\n", i);}return 0;} } 答&#xff1a;一直输出5&#xff0c;死循环 解析&#xff1a;i5是赋值语句&#xff0c;不是判断语句&#xff0c;每一次循…

类与对象中C++

加油&#xff01;&#xff01;&#xff01; 文章目录 前言 一、类的6个默认成员函数 ​编辑 二、构造函数 1.概念 三、析构函数 1.概念 2.特性 四、拷贝构造函数 1.概念 2.特征 拷贝构造函数典型调用场景 五、赋值运算符重载 1.运算符重载 2.赋值运算符重载 赋值运算符重载格式…

gitee规范团队 代码提交

1.团队开会规范 使用 插件 &#xff1a; git Commit Message Helper 插件进行代码提交前规范 2.gitee代码仓库断控制&#xff0c;上面只是规范了程序员开发端&#xff1b;但是gitee也要管理控制&#xff1b;正则根据每个公司的不同来进行。

基于Springboot+vue的宠物销售商城网站

摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;宠物销售商城当然也不能排除在外。宠物销售商城是以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#x…

【目录整理】(五)

​​​​​Git 基础 Git 详细安装教程文章浏览阅读10w次&#xff0c;点赞9.6k次&#xff0c;收藏1.7w次。Git 是个免费的开源分布式版本控制系统&#xff0c;下载地址为git-scm.com 或者 gitforwindows.org&#xff0c;本文介绍 Git-2.40.0-64-bit.exe 版本的安装方法&#x…