【数据结构】第七节:堆

 个人主页 深情秋刀鱼@-CSDN博客 

 数据结构专栏:数据结构与算法

源码获取:数据结构: 上传我写的关于数据结构的代码 (gitee.com)

目录

一、堆

1.堆的概念

2.堆的定义

二、堆的实现

1.初始化和销毁

2.插入

向上调整算法

3.删除

向下调整算法

4.取堆顶元素

5.判空

三、Top_k问题

1.问题描述

2.面试中的Top_k问题

四、堆排序

1.建堆

 2.堆排序

五、堆的时间复杂度

1.建堆

a.树中高度与节点的关系

b.向下调整建堆算法

c.向上调整建堆算法

 2.堆排序


一、堆

1.堆的概念

         堆是一棵完全二叉树,且其中的节点总是不大于(或不小于某个值)。如果堆中的节点总是不大于某个值(根节点最大),称为大根堆;如果堆中的节点总是不小于某个值(根节点最小)将根节点最小的堆称为小根堆。

        大根堆和小根堆描述的是双亲节点和子节点之间的关系,而子节点之间没有直接的联系。

2.堆的定义

typedef int HPDataType;

//堆
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}Heap;

        二叉树一般可以使用两种结构存储,一种顺序结构(数组),一种链式结构(链表)。由于堆是一棵完全二叉树,用数组结构存储较为简洁。

        数组中双亲节点和子节点之间的关系:

  • 当双亲结点的下标为i时,左子节点的下标=2 * i + 1,右子节点的下标=2 * i + 2
  • 当子节点的下标为i时,双亲节点的下标=(i - 1)/ 2

二、堆的实现

1.初始化和销毁

//初始化
void HPInit(Heap* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

//销毁
void HPDestroy(Heap* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

2.插入

        堆在内存中是以数组的形式存储的,在逻辑上需要将数组看成一棵完全二叉树。向堆中插入数据时要保证堆的结构不被破坏,并将其调整为小根堆或大根堆时需要用到向上调整算法

向上调整算法

        使用前提:左右子树必须是一个堆,才能调整。

        算法实现:以小根堆为例,在数组尾部(下标为size-1)的位置插入数据(记下标为child),被插入数据child通过下标之间的关系找到child所在的这棵子树的根(记下标为parent)并与根节点比较,如果a[child]<a[parent]说明此时双亲节点大于子结点的,不符合小根堆的性质,此时需要交换child与parent的位置并更新child和parent的值,一直到堆顶(下标为0)则调整结束。

//交换
void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *b;
	*b = *a;
	*a = tmp;
}

//向上调整算法(小根堆)
void AdjustUP(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[parent] > a[child])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;//更新下标值
		}
		else
			break;
	}
}
  •  图解(小根堆):

  • 代码实现:
//插入
void HPPush(Heap* 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!");
			return;
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size++] = x;
	AdjustUP(php->a, php->size - 1);
}

3.删除

        删除规定只删除堆顶元素(删除堆尾元素size--即可),删除堆顶元素的同时需要保持结构不变,需要用到向下调整算法。

向下调整算法

        使用前提:左右子树必须是一个堆,才能调整。

        算法实现:以小根堆为例,将首(B)尾(A)元素交换,在尾部删除堆顶元素B,在堆顶的尾元素A通过向下调整算法调整到合适的位置再形成堆。新的堆顶元素A下标为0(记为parent),以parent为根的两个子节点分别为左child节点(下标2*parent+1)、右child节点(下标2*parent+2),为满足小根堆的性质,我们需要在这两个节点中找到较小的一个与元素A交换成为新的根,元素A成为子节点后再向下寻找以元素A为根的两个子节点,一直到堆底调整结束。

//向下调整算法
void AdjustDown(HPDataType* a, int n, int parent)
{
    //假设法
	int child = 2 * parent + 1;
	while (child < n)
	{
        //两个子节点中较小的那个(注意边界的处理)
		if (child + 1 < n && a[child] > a[child + 1])
			child++;
		if (a[parent] > a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
			break;
	}
}

        在判断两个子节点的大小时不妨先假设左子节点大,进入循环后再判断左右子节点的大小。

  • 图解(小根堆): 

  •  代码实现:
//删除(删除堆顶的数据)
void HPPop(Heap* php)
{
	assert(php && php->size > 0);
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	AdjustDown(php->a, php->size, 0);
}

4.取堆顶元素

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

5.判空

//判空
bool HPEmpty(Heap* php)
{
	assert(php);
	return php->size == 0;
}

三、Top_k问题

1.问题描述

        TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。 比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
        对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前K个元素来建堆
  • 前k个最大的元素,则建小堆
  • 前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

        简单来说:以求取数组中前六个最大的元素为例,将一个元素个数为n的数组调整为大堆后,堆顶的元素就是数组中n个元素的最大值,获取堆顶元素后将堆顶元素删除(删除的步骤是堆顶元素先与堆尾元素交换,在堆尾删除堆顶元素),通过向下调整算法调整堆的结构使其仍然呈大堆排列,排列之后新的堆顶元素就是数组中n-1个元素中的最大值,依此类推。

  • 代码实现:
int a[] = { 1,2,3,5,4,9 };
int k;//前k个
scanf_s("%d", &k);
while (k--)
{
	printf("%d ", HPTop(&hp));
	HPPop(&hp);
}
  •  运行结果

2.面试中的Top_k问题

C语言:文件操作详解-CSDN博客

  • 给出N个整数,存储在磁盘文件中,要求取出最大的前k个元素。

        这个问题属于最常规的Top_k问题,建大堆然后依次popk个元素即可。但是面试中往往不会这么简单,这种方法固然存在一定的缺陷:当N过于大时,占用内存空间较多,如果给出10亿个整数就需要占用将近4G的内存空间,如果面试官对内存空间做出限制,显然这种方法就行不通了。

  • 给出N个整数,存储在磁盘文件中,要求取出最大的前k个元素且占用的内存空间不允许超过1KB。

        介绍一种很巧妙的方法:取前k个元素建小堆,然后用剩下的N-k个元素与堆顶元素比较,如果大于堆顶元素则直接覆盖堆顶元素,成为新的堆顶元素,最后用向下调整算法调整结构,依次遍历完所有的数据。这样留在堆中的元素就是最大的前k个元素。

//在text中创建N个数据
void CreateN()
{
	int n;
	scanf("%d", &n);
	srand((unsigned int)time(0));
	const char* FileName = "D:\\Git code\\data-structure\\Project_Heap\\Project_Heap\\data.txt";//文件地址
	FILE* fin = fopen(FileName, "w");
	if (fin == NULL) {
		perror("fopen fail");
		return;
	}
	for (int i = 1; i <= n; i++) {
		int x = (rand() + i) % 10000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}

//Top_k
void Test3()
{
	CreateN();
	const char* FileName = "D:\\Git code\\data-structure\\Project_Heap\\Project_Heap\\data.txt";
	FILE* fout = fopen(FileName, "r");
	int k;
	scanf("%d", &k);
	int* kMinHeap = (int*)malloc(sizeof(int) * k);
	if (kMinHeap == NULL) {
		perror("malloc fail");
		return;
	}
	//将文件中的数据(前k个)读取到数组中
	for (int i = 0; i < k; i++) {
		fscanf(fout, "%d", &kMinHeap[i]);
	}
	//建堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--) {
		AdjustDown(kMinHeap, k, i);
	}
	int x;
	while (fscanf(fout, "%d", &x) > 0) {
		if (x > kMinHeap[0]) {
			kMinHeap[0] = x;
			AdjustDown(kMinHeap, k, 0);
		}
	}
	for (int i = 0; i < k; i++) {
		printf("%d\n", kMinHeap[i]);
	}
	fclose(fout);
}

四、堆排序

1.建堆

        给定一个数组,要求将其调整为大堆或小堆。我们可以将原数组直接看成一棵完全二叉树,然后利用向上或向下调整算法将其调整为大堆或小堆,大堆和小堆是可以自由切换的,只需要更改向下和向上调整算法中的比较逻辑即可。

  • 向上调整算法建小堆
int a[] = { 2,3,1,4,6,5,9 };
Heap hp;
HPInit(&hp);
int n = sizeof(a)/xizeof(int);
for (int i = 1; i < n; i++)
	AdjustUP(a, i);

        建堆逻辑:总是保证前i个数据具有堆的性质,当i=n时,整棵树都具有了堆的性质。


  • 向下调整算法建小堆
int a[] = { 2,3,1,4,6,5,9 };
Heap hp;
HPInit(&hp);
int n = sizeof(a)/sizeof(int);
for (int i = (n - 1 - 1) / 2; i < n; i++)
	AdjustDown(a, n, i);

        在向下调整算法建堆时,我们从倒数的第一个非叶子结点的子树开始调整,一直调整到根结点的树,就可以调整成堆。

        建堆逻辑:总是保持后i个数据具有堆的性质,当i=0时,整棵树都具有了堆的性质。


  • 图解(大根堆):

 2.堆排序

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

1. 建堆
  • 升序:建大堆
  • 降序:建小堆
2. 利用堆删除思想来进行排序:建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
        算法逻辑:以降序建小堆为例,一个元素个数为n的数组调整为小堆后,堆顶元素是数组中的最小元素,将堆顶元素与堆尾交换,然后对新的堆顶元素向下调整,一直调整到合适的位置再形成堆,此时堆顶元素应为数组中次小的元素,将次小的元素与第n-1个元素(倒数第二个)交换,再利用向下调整算法调整结构,依此类推。
        代码实现:
//向下调整算法
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] > a[child + 1])
			child++;
		if (a[parent] > a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
			break;
	}
}

//交换
void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *b;
	*b = *a;
	*a = tmp;
}


//堆排序(O(N*logN))
void HPSort(HPDataType* a, int n)
{
	//降序:建小堆
	//升序:建大堆
	//for (int i = 1; i < n; i++)
	//	AdjustUP(a, i);//向上调整建堆
	for (int i = (n - 1 - 1) / 2; i < n; i++)
		AdjustDown(a, n, i);
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

图解(升序大根堆):

五、堆的时间复杂度

1.建堆

a.树中高度与节点的关系

        设有一棵高度为h的满二叉树,如下图:

根据递推公式我们可以得到节点N与高度h的关系:F(h)=2^0+2^1+2^2+.....+2^(h-1)。根据等比数列求和公式,F(h)=2^h-1。


        一棵完全二叉树节点最多的情况是一棵满二叉树(最后一层全满),节点最少的情况是最后一层有且仅有一个节点的情况。

  • 满二叉树:F(h)=2^h-1=N——h=log(N+1)
  • 完全二叉树节点最少情况:F(h)=2^(h-1)=N——h=logN+1

        综上完全二叉树的节点应在log(N+1)与logN+1之间,根据大O的渐进表示法为logN。


b.向下调整建堆算法

        在向下调整建堆的过程中,我们选择从最后一个非叶子节点的节点开始调整,在计算时间复杂度时,只考虑最坏的情况,将堆简化看作一棵满二叉树,即每个双亲节点都需要调整到最底部,如第一层2^0个节点向下移动4次,第二层2^1个节点向下移动2层,第三层2^2个节点向下移动1次。

综上,向下调整建堆得时间复杂度为O(N)。

c.向上调整建堆算法

        在向上调整建堆中,我们选择从第一个子节点开始调整。还是只考虑最坏的情况并将堆简化为一棵满二叉树。从第2个节点开始,每个节点都需要向上调整高度次,即第二层2^1个节点向上移动1次,第三层2^2个节点向上移动2次,第四层2^3个节点(看作满二叉树)向上移动3次。

综上,向下调整建堆得时间复杂度为O(N*logN)。

 2.堆排序

建堆时间复杂度对比:

  • 向上调整建堆:O(N*logN)
  • 向下调整建堆:O(N)

堆排序的实现:

void HPSort(HPDataType* a, int n)
{
	//降序:建小堆
	//升序:建大堆
	//for (int i = 1; i < n; i++)
	//	AdjustUP(a, i);//向上调整建堆
	for (int i = (n - 1 - 1) / 2; i < n; i++)
		AdjustDown(a, n, i);
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

        建堆结束后,类比调整算法的推导可以得出排序的时间复杂度是O(N*logN)。  

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

9.STL中list的常见操作(图文并茂)

目录 1.list的介绍及使用 1.1.list的构造 1.2 list iterator的使用 1.3. list capacity 1.4.list modifiers 1.5.list的迭代器失效 1.list的介绍及使用 list介绍 &#xff0c;可以通过以下图直观的感受到 vector 和 list 的区别 Vector 插入代价高&#xff0c;但便于排…

LabVIEW与串口通讯在运行一段时间后出现数据接收中断的问题

这些问题可能与硬件、软件或通信协议有关。以下是详细的原因分析和可能的解决方案&#xff1a; 一、硬件原因 串口线缆或接口问题&#xff1a; 由于长时间使用&#xff0c;串口线缆可能出现接触不良或损坏。接口松动也可能导致通讯中断。 解决方案&#xff1a;检查并更换串口…

【区块链】智能合约漏洞测试

打开Ganache vscode打开智能合约漏洞工程 合约内容 pragma solidity >0.8.3;contract EtherStore {mapping(address > uint) public balances;function deposit() public payable {balances[msg.sender] msg.value;emit Balance(balances[msg.sender]);}function with…

完成商品属性分组和商品属性关联维护

文章目录 1.前端页面搭建1.复制attrgroup-attr-relation.vue到src/views/modules/commodity下2.加入超链接和引入组件 src/views/modules/commodity/attrgroup.vue1.加入超链接2.引入组件 3.数据池加入变量4.使用组件1.引用组件2.添加方法3.测试&#xff0c;点击关联&#xff0…

【笔记】Qt 按钮控件介绍(QPushButton,QCheckBox,QToolButton)

文章目录 QAbstractButton 抽象类(父类)QAbstractButton 类中的属性QAbstractButton 类中的函数QAbstractButton 类中的信号QAbstractButton 类中的槽 QPushButton 类(标准按钮)QPushButton 类中的属性QPushButton 类中的函数、槽 QCheckBox 类(复选按钮)QCheckBox 类的属性QCh…

【全部更新完毕】2024电工杯A题数学建模详细思路代码文章分享

A 题&#xff1a;园区微电网风光储协调优化配置 摘要 在全球范围内&#xff0c;气候变化和环境污染问题日益严重&#xff0c;减少碳排放和实现可持续发展成为各国的共同目标。新能源&#xff0c;尤其是风能和光伏发电&#xff0c;因其清洁、可再生的特性&#xff0c;正在全球范…

国产化服务器设计 原理图:905-多路PCIe的阵列计算全国产化服务器

多路PCIe的阵列计算全国产化服务器 多路PCIe的阵列计算全国产化服务器以国产化处理器&#xff08;海光、飞腾ARM、算能RSIC V&#xff09;为主板&#xff0c;扩展6-8路PCIe3.0X4计算卡&#xff1b; 计算卡为全国产化的AI处理卡&#xff08;瑞星微ARM&#xff0c;算能AI&#x…

C++语言学习(五)—— 类与对象(一)

目录 一、类类型的定义 二、类成员的访问控制 2.1 什么是"类内"和"类外" 2.2 对于访问控制属性的说明 三、类类型的使用 3.1 进行抽象 3.2 声明类 3.3 实现类 3.4 使用类 四、构造函数的引入 五、析构函数的引入 六、重载构造函数的引入 6.1 …

权限维持--windows

隐藏文件 ①文件属性隐藏 如何排查&#xff1a; 使用dir命令无法看到有特殊属性的文件需使用/a ②真隐藏 相当于给原本的文件增加系统文件属性、存档文件属性、只读文集属性、隐藏文件属性 如何排查&#xff1a; 取消受保护的操作系统文件 ③利用ADS隐藏 使用数据流 echo &…

我把PostgreSQL最核心的插件撸干净了!!!

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复&#xff0c; 安装迁移&#xff0c;性能优化、故障…

USB抓包工具:bushound安装及使用

一、环境搭建 下载busbound6.01安装包&#xff0c;安装完成&#xff0c;重启电脑。 二、工具配置 按照下图配置工具&#xff1a; 使能自动识别新设备 2. 设置抓取数据的容量 三、抓包 回到capture选项卡&#xff0c;在页面的右下角有个run的按钮&#xff0c;点击使能&…

Multi-Attention Transformer for Naturalistic Driving Action Recognition

标题&#xff1a;用于自然驾驶行为识别的多注意力Transformer 源文链接&#xff1a;https://openaccess.thecvf.com/content/CVPR2023W/AICity/papers/Dong_Multi-Attention_Transformer_for_Naturalistic_Driving_Action_Recognition_CVPRW_2023_paper.pdfhttps://openaccess…

快速幂算法6

eg: n10&#xff0c;10%20, 10/25, 5%21,4* 5/22, 2%20,4*256 0/20, 1024 递归算法 #include<iostream> using namespace std; long long quick_pow(int b,int e) {if(b0)return 0;if(e0)return 1;if(e%20){int tempquick_pow(b,e/2);return temp*temp;}if(e%2!0)…

MATLAB modem.qammod和randint函数

实在是受不了MATLAB改函数了 试图找到如何修改代码&#xff0c;诶嘿&#xff0c;失败了&#xff0c;那我就自己写一下吧 randint函数 %% P pre-MMSE clear all;clc % 参数设置 N_frame 100; %帧数 N_packet 1000; % 分组数 b 2; % 每符号比特数 M 2 ^ b; % 调制阶数 mod…

Flutter笔记:Widgets Easier组件库-使用隐私守卫

Flutter笔记 Widgets Easier组件库&#xff1a;使用隐私守卫 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https:…

local dimming(局部调光)介绍

文章目录 1. 什么是local dimming2. 工作原理3. 类型4. 优点5. 缺点和局限7. 技术发展趋势 1. 什么是local dimming local dimming&#xff08;局部调光&#xff09;是电视和显示器中用于提升画面对比度和画质的背光技术。其基本原理是将背光源&#xff08;通常是LED&#xff…

【3dmax笔记】001:3dmax2020下载及安装教程

一、3dmax2020安装教程 1、双击运行exe文件&#xff0c;提取安装程序到指定文件夹&#xff1b; 2、提取完成&#xff0c;安装程序启动&#xff0c;点击安装 3、点击接受许可协议&#xff1b; 4、设置安装位置&#xff0c;点击安装&#xff1b; 5、等待产品安装&#xff1b; 6、…

C++:vector基础讲解

hello&#xff0c;各位小伙伴&#xff0c;本篇文章跟大家一起学习《C&#xff1a;vector基础讲解》&#xff0c;感谢大家对我上一篇的支持&#xff0c;如有什么问题&#xff0c;还请多多指教 &#xff01; 如果本篇文章对你有帮助&#xff0c;还请各位点点赞&#xff01;&#…

【C++】牛客——OR64 求和

✨题目链接&#xff1a; OR64 求和 ✨题目描述 输入两个整数 n 和 m&#xff0c;从数列1&#xff0c;2&#xff0c;3.......n 中随意取几个数,使其和等于 m ,要求将其中所有的可能组合列出来 ✨输入描述: 每个测试输入包含2个整数,n和m ✨输出描述: 按每个组合的字典序排列…

如何使用 CapSolver 扩展找到 Google reCAPTCHA 站点密钥?

网站安全性在当今至关重要&#xff0c;Google reCAPTCHA 作为防止垃圾邮件和滥用行为的前线防御系统起着关键作用。reCAPTCHA 站点密钥是确保网站交互由人类驱动的唯一标识符。了解如何找到这个密钥对于网站管理员和开发人员来说至关重要。 什么是 reCAPTCHA 站点密钥 reCAPT…