数据结构——堆和优先队列

news2025/1/15 17:25:46

文章目录

  • 前言
    • 堆的引入
    • 堆的定义
    • 堆的储存结构
  • 优先队列
    • 优先队列简介
    • 优先队列的基础操作
      • 入队
      • 出队
    • 优先队列的实现
  • 堆的应用
    • 堆排序
    • TOP-K问题
      • 什么是TOP-K问题
      • TOP-K问题的排序解法
      • TOP-K问题的堆解法
  • 总结


前言

堆是一个比较基础,且实现起来难度也不算太大的一个数据结构。而且堆在很多地方都有较好的应用。


堆的引入

考虑一颗完全二叉树,如果你要从里面找到值最大的数据,怎么找?是不是得遍历整棵树,才能找到一个值。这样做的复杂度为 O ( n ) O(n) O(n)。如果要在这棵树中频繁查找最值的话,这样的效率显然不能满足我们的需求。由此想到,也许我们可以对这颗树进行一些处理,让数据按照某种规则排列,从而方便查找最值。而堆就是这样一种数据结构。

堆的定义

堆作为一种数据结构,底下有很多具体的分支,比如二项堆和斐波那契堆。现在我们介绍一种最最基础的堆——二叉堆。在许多地方,二叉堆又简称为堆。在本文中,如无特殊声明,堆默认指二叉堆。
二叉堆是具有下列性质的完全二叉树:每个结点的值都小于或等于其左右孩子结点的值(称为小根堆);或者每个结点的值都大于或等于其左右孩子的值(称为大根堆)。以小根堆为例(下面的代码也是以小根堆为例),如果将堆按层序从1开始编号,则结点之间满足如下关系:
{ k i ≤ k 2 i k i ≤ k 2 i + 1 ( 1 ≤ i ≤ ⌊ n / 2 ⌋ ) \begin{cases} k_i\leq k_{2i} \\ k_i\leq k_{2i+1} \tag{$1\leq i\leq \lfloor n/2 \rfloor$} \end{cases} {kik2ikik2i+1(1in/2)

堆的储存结构

由于堆是完全二叉树,因此采用顺序存储。值得一提的是,堆实际上是一种树结构,但堆的经典实现方法是使用数组。堆按层序从1开始连续编号,将结点以编号为下标存储到一维数组中。若一个结点的编号为 i i i,则它的左儿子结点编号为 2 i 2i 2i,右儿子结点编号为 2 i + 1 2i+1 2i+1,父亲结点编号为 ⌊ i / 2 ⌋ \lfloor i/2 \rfloor i/2

typedef struct
{
	DataType data[Maxsize];
	int rear;
} PriQueue;

优先队列

优先队列简介

优先队列是按照某种优先级进行排列的队列,优先级越高的元素出队越早,优先级相同者按照先进先出的原则进行处理。优先队列的基本算法可以在普通队列的基础上修改而成。例如,入队时将元素插入到队尾,出队时找出优先级最高的元素出队;或者入队时将元素按照优先级插入到合适的位置,出队时将队头元素出队。这两种实现方法,入队或出队总有一个时间复杂度为 O ( n ) O(n) O(n)。采用堆来实现优先队列,入队(即插入结点)和出队(即删除根结点)的时间复杂度均为 O ( l o g 2 n ) O(log_2 n) O(log2n)

优先队列的基础操作

入队

优先队列的入队操作,即堆的插入结点操作。
在数组的最末尾插入新结点。然后自下而上调整子节点与父结点:比较当前结点与父结点,不满足堆性质则交换。从而使得当前子树满足二叉堆的性质。时间复杂度为 O ( l o g 2 n ) O(log_2 n) O(log2n)
入队

void EnQueue(PriQueue *Q,DataType x)
{
	int i,temp;
	if (Q->rear==Maxsize-1) {printf("上溢");exit(-1);}
	Q->rear++;
	i=Q->rear;
	Q->data[i]=x;
	while(i/2>0&&Q->data[i/2]>x)
	{
		temp=Q->data[i];
		Q->data[i]=Q->data[i/2];
		Q->data[i/2]=temp;
		i=i/2;
	}
}

出队

优先队列的出队操作,即堆的删除根结点操作。删除根结点之后,需要维护堆的性质。
对于最大堆,删除根结点就是删除最大值;对于最小堆,是删除最小值。然后,把堆存储的最后那个结点移到填在根结点处。再从上而下调整父结点与它的子结点:对于最大堆,父结点如果小于具有最大值的子节点,则交换二者。直至当前结点与它的子节点满足堆性质为止。时间复杂度也是 O ( l o g 2 n ) O(log_2 n) O(log2n)
出队

DataType DeQueue(PriQueue *Q)
{
	int i,j,x,temp;
	if (Q->rear==0) {printf("下溢");exit(-1);}
	x=Q->data[1];
	Q->data[1]=Q->data[Q->rear--];
	i=1;
	j=2*i;
	while (j<=Q->rear)
	{
		if (j<Q->rear&&Q->data[j]>Q->data[j+1]) j++;
		if (Q->data[i]<Q->data[j]) break;
		else
		{
			temp=Q->data[i];
			Q->data[i]=Q->data[j];
			Q->data[j]=temp;
			i=j;
			j=2*i;
		}
	}
	return x;
}

优先队列的实现


堆的应用

堆排序

堆这个数据结构可以用于排序。只需要用小根堆建立一个优先队列,然后把所有数据入队,依次出列的结果就是所有数据从小到大的排序结果。
我们来计算一下堆排序的算法复杂度。由于入队和出队是对称操作,故只需考虑入队就行了。每次入队的复杂度为当前队列中元素的对数,即若当前队列中有i个元素,那么当前元素入队时的时间复杂度为 O ( l o g 2 i ) O(log_2 i) O(log2i)。共n个元素,那么入队时总的复杂度为 O ( ∑ i = 1 n l o g 2 i ) = O ( l o g 2 ∏ i = 1 n i ) = O ( l o g 2 n ! ) O(\sum_{i=1}^n{log_2 i})=O(log_2{\prod_{i=1}^n{}i})=O(log_2{n!}) O(i=1nlog2i)=O(log2i=1ni)=O(log2n!)。根据斯特林公式,当n趋于无穷大时, n ! ≈ 2 π n ( n e ) n n!\approx \sqrt{2\pi n}(\frac{n}{e})^n n!2πn (en)n,可以得到 O ( l o g 2 n ! ) = O ( l o g 2 2 π n ( n e ) n ) = O ( l o g 2 2 π n + n l o g 2 n e ) = O ( n l o g 2 n ) O(log_2{n!})=O(log_2{\sqrt{2\pi n}(\frac{n}{e})^n})=O(log_2{\sqrt{2\pi n}}+nlog_2{\frac{n}{e}})=O(nlog_2{n}) O(log2n!)=O(log22πn (en)n)=O(log22πn +nlog2en)=O(nlog2n)
于是得到堆排序的时间复杂度为 O ( n l o g 2 n ) O(nlog_2{n}) O(nlog2n)
代码如下:

#include<iostream>
#include<cstdio>
using namespace std;
#define Maxsize 100
typedef int DataType;
typedef struct
{
	DataType data[Maxsize];
	int rear;
} PriQueue;
void EnQueue(PriQueue *Q,DataType x)
{
	int i,temp;
	if (Q->rear==Maxsize-1) {printf("上溢");exit(-1);}
	Q->rear++;
	i=Q->rear;
	Q->data[i]=x;
	while(i/2>0&&Q->data[i/2]>x)
	{
		temp=Q->data[i];
		Q->data[i]=Q->data[i/2];
		Q->data[i/2]=temp;
		i=i/2;
	}
}
DataType DeQueue(PriQueue *Q)
{
	int i,j,x,temp;
	if (Q->rear==0) {printf("下溢");exit(-1);}
	x=Q->data[1];
	Q->data[1]=Q->data[Q->rear--];
	i=1;
	j=2*i;
	while (j<=Q->rear)
	{
		if (j<Q->rear&&Q->data[j]>Q->data[j+1]) j++;
		if (Q->data[i]<Q->data[j]) break;
		else
		{
			temp=Q->data[i];
			Q->data[i]=Q->data[j];
			Q->data[j]=temp;
			i=j;
			j=2*i;
		}
	}
	return x;
}
int main()
{
	int n,x;
	PriQueue Q;
	Q.rear=0;
	cin>>n;
	for (int i=1;i<=n;i++)
	{
		cin>>x;
		EnQueue(&Q,x);
	}
	for (int i=1;i<=n;i++)
	{
		cout<<DeQueue(&Q)<<' ';
	}
} 

这其实不是标准的堆排序写法。标准的堆排序建堆的复杂度是 O ( n ) O(n) O(n),但总的复杂度仍为 O ( n l o g 2 n ) O(nlog_2{n}) O(nlog2n)。我这种直接采用优先队列入队操作来建堆的写法实现起来比较简单,又不影响时间复杂度。

TOP-K问题

什么是TOP-K问题

给定n个数据,求前K个最大(最小)的元素。
比如求专业前10名,世界500强等,都属于TOP-K问题。

TOP-K问题的排序解法

有一种很简单的解法,即排完序之后输出前K个元素。一般来说,现有的最好的排序算法复杂度为 O ( n l o g 2 n ) O(nlog_2{n}) O(nlog2n),那么这样做的复杂度就也是 O ( n l o g 2 n ) O(nlog_2{n}) O(nlog2n)。一般这个问题中n都特别大,而K比较小。所以我们可以改进一下这个算法。

TOP-K问题的堆解法

假设要求最大的K个元素。我们可以建立一个小根堆,保持堆中的元素始终为K个。这个堆表示的含义是当前元素中,最大的K个元素。先把前K个元素加入堆中,然后一个一个考虑后面的元素:若该元素比小根堆的堆顶大,就删除根结点,然后把当前元素插入堆中。通过这个操作,始终可以保证堆中的元素一定是当前考虑过的所有元素中最大的K个。代码如下:

#include<iostream>
#include<cstdio>
using namespace std;
#define Maxsize 100
typedef int DataType;
typedef struct
{
	DataType data[Maxsize];
	int rear;
} PriQueue;
void EnQueue(PriQueue *Q,DataType x)
{
	int i,temp;
	if (Q->rear==Maxsize-1) {printf("上溢");exit(-1);}
	Q->rear++;
	i=Q->rear;
	Q->data[i]=x;
	while(i/2>0&&Q->data[i/2]>x)
	{
		temp=Q->data[i];
		Q->data[i]=Q->data[i/2];
		Q->data[i/2]=temp;
		i=i/2;
	}
}
DataType DeQueue(PriQueue *Q)
{
	int i,j,x,temp;
	if (Q->rear==0) {printf("下溢");exit(-1);}
	x=Q->data[1];
	Q->data[1]=Q->data[Q->rear--];
	i=1;
	j=2*i;
	while (j<=Q->rear)
	{
		if (j<Q->rear&&Q->data[j]>Q->data[j+1]) j++;
		if (Q->data[i]<Q->data[j]) break;
		else
		{
			temp=Q->data[i];
			Q->data[i]=Q->data[j];
			Q->data[j]=temp;
			i=j;
			j=2*i;
		}
	}
	return x;
}
int main()
{
	int n,k,x;
	PriQueue Q;
	Q.rear=0;
	cin>>n>>k;
	for (int i=1;i<=k;i++)
	{
		cin>>x;
		EnQueue(&Q,x);
	}
	for (int i=k+1;i<=n;i++)
	{
		cin>>x;
		if (x>Q.data[1])
		{
			DeQueue(&Q);
			EnQueue(&Q,x);
		}
	}
	int a[k+1];
	for (int i=1;i<=k;i++) a[k+1-i]=DeQueue(&Q);
	for (int i=1;i<=k;i++) cout<<a[i]<<' ';
} 

由于堆的大小为K,所以这个算法的时间为 O ( n l o g 2 K ) O(nlog_2 K) O(nlog2K)。在n较大,K较小时,这个算法还是比一般算法要快不少的。

总结

本文主要介绍了堆和优先队列的概念、具体实现及其应用。堆和优先队列在各个领域中有着广泛的应用。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高性能RPC框架:TARS简介、设计思想、架构、特性学习

文章目录 一、TARS简介二、设计思想三、整体架构3.1 架构拓扑3.2 服务交互流程3.3 Web管理系统3.4 服务结构 四、Tars特性4.1 Tars协议4.2 调用方式4.3 负载均衡4.4 容错保护4.5 过载保护4.6 消息染色4.7 IDC分组4.8 SET分组4.9 数据监控4.10 集中配置 声明&#xff1a;以下内容…

文心一言 vs GPT-4 —— 全面横向比较

文心一言 vs GPT-4 —— 全面横向比较 3月15日凌晨&#xff0c;OpenAI发布“迄今为止功能最强大的模型”——GPT-4。我第一时间为大家奉上了体验报告《OpenAI 发布GPT-4——全网抢先体验》。 时隔一日&#xff0c;3月16日下午百度发布大语言模型——文心一言。发布会上&#…

开放式蓝牙耳机推荐,列举出几款值得入手的开放式蓝牙耳机

随着耳机市场的发展&#xff0c;骨传导耳机的出现也逐渐受到了市场的认可&#xff0c;骨传导耳机&#xff0c;是通过颅骨来进行声音传导的一种耳机。与传统的入耳式耳机不同&#xff0c;骨传导耳机不需要将耳朵堵上&#xff0c;而是通过颅骨震动将声音传到内耳&#xff0c;所以…

IDEA快速部署Spring Boot 项目到Docker

IDEA快速部署Spring Boot 项目到Docker 文章目录 IDEA快速部署Spring Boot 项目到Docker一、IDEA 连接 Docker自己的虚拟机远程服务器 二、Maven插件与Dockerfiledocker-maven-pluginDockerfile 三、项目打包上传镜像四、容器的创建与运行容器的创建环境的检查访问项目检验 一、…

4月18日第壹简报,星期二,农历闰二月廿八

4月18日第壹简报&#xff0c;星期二&#xff0c;农历闰二月廿八坚持阅读&#xff0c;静待花开1. 《中国卫生健康发展评价报告&#xff08;2022&#xff09;》蓝皮书发布&#xff0c;排名前十依次为&#xff1a;北京、深圳、杭州、上海、青岛、武汉、昆明、广州、厦门和宁波。2.…

【Docker学习三部曲】——进阶篇

Compose 1️⃣ 什么是 Docker-Compose ? Docker Compose 是Docker官方提供的一个用于定义和运行多个容器的工具&#xff0c;它采用了声明式的语法定义单个应用程序的多个容器以及它们之间的相互关系和依赖关系。 使用Docker Compose&#xff0c;您可以通过一个配置文件来管…

消防规范图集大全

总说明 A-800X650 (1)箱体长.煌尺寸代号(尺寸单位:mm) B-1000X700; C-1200X750 D-带灭火器箱组合式消防柜; E-非标准箱。 1本图集是依据现行有关国家标准和规范 在1999年编制的《室内消火栓安装》 (2)水带安置方式代号 (99S202)全国通用给水排水标准图集的基础上重新编制的。 P…

理解TreeMap结构及其实现

TreeMap是基于红黑树&#xff08;Red-Black tree&#xff09;的 NavigableMap 实现(是自平衡的二叉树)。该映射根据其键的自然顺序进行排序&#xff0c;或者根据创建映射时提供的 Comparator 进行排序&#xff0c;具体取决于使用的构造方法。 一、对外开放API TreeMap提供了保证…

GPT模型支持下的Python-GEE遥感云大数据分析、管理与可视化技术及多领域案例实践

随着航空、航天、近地空间等多个遥感平台的不断发展&#xff0c;近年来遥感技术突飞猛进。由此&#xff0c;遥感数据的空间、时间、光谱分辨率不断提高&#xff0c;数据量也大幅增长&#xff0c;使其越来越具有大数据特征。对于相关研究而言&#xff0c;遥感大数据的出现为其提…

全网最全的快速排序方法--Hoare快排 挖坑法快排 二路快排 三路快排 非递归快排

目录 一.快速排序 1.基本介绍 2.基本思想 二.Hoare快排 0.前情知识 1.交换数组中的两个元素 2.指定范围的插入排序 1.基本思路 2.代码实现 3.优化思路 三.挖坑法快排(校招中适用) 1.基本思路 2.代码实现 四.二路快排 1.基本思路 2.代码实现 3.优化思路 五.三…

浅谈ChatGPT(人工智能)

带你了解ChatGPT 1.ChatGPT是什么2.ChatGPT的特点3.ChatGPT的用途4.ChatGPT出现给社会带来的影响5.ChatGPT存在的问题6.ChatGPT的未来发展趋势7.总结 1.ChatGPT是什么 ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;是美…

如何通过开源项目搭建私有云平台--第三步:部署镜像仓库

第三步 部署镜像仓库 采用开源的harbor来进行部署&#xff0c;分别在两台服务器进行部署&#xff0c;然后实现两个镜像仓库数据同步 具体部署环境如下&#xff1a; 10.10.10.3 主harbor 操作系统&#xff1a; centos 8 10.10.10.4 备用harbor 操作系统&#xff1a;cen…

【使用者手册】手动改善IntelliJ IDEA和Scala插件性能

IntelliJ IDEA&#xff0c;是java编程语言开发的集成环境。IntelliJ在业界被公认为最好的java开发工具&#xff0c;尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能可以说是超常的。 在…

解决webassembly pthread 子线程调用主线程js问题

解决webassembly pthread 子线程调用主线程js问题 背景&#xff1a; web端项目做了一段时间后&#xff0c;我们需求是加载工程是异步的&#xff0c;主线程会调用wasm方法&#xff0c;wasm内部用pthread创建出来线程&#xff0c;然后在这个线程里边处理任务&#xff0c;处理完…

园区智慧导览地图软件,智慧工厂导航定位怎么解决方案的

智慧工厂导航定位怎么解决方案的地图新基建是行业的核心数字基础需求之一&#xff0c;行业内中已构建了较为完整的城市级地理信息系统。园区管理涉及众多方面&#xff0c;因此园区的智慧信息化建设至关重要&#xff0c;需求越来越广泛。在智慧园区中&#xff0c;基于园区的电子…

【C++类和对象】类和对象(上){初识面向对象,类的引入,类的定义,类的访问限定符,封装,类的作用域,类的实例化,类对象模型,this指针}

一、面向过程和面向对象初步认识 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 C是基于面向对象的&#xff0c;关注的是对象&#xff0c;将一件事情拆分成不同的对象&#xff0c;靠对象之间的交互完成。…

瑞吉外卖:项目介绍和环境搭建

文章目录 软件开发基础软件开发流程角色分工软件环境 瑞吉外卖项目介绍项目介绍开发流程技术选型功能架构角色 环境搭建 软件开发基础 软件开发流程 需求分析&#xff1a;产品原型&#xff08;大体结构、页面、功能等&#xff09;和需求规格说明书设计&#xff1a;产品文档、…

TCP/UDP的头部字段细节

目录 TCP头部字段 一、源端口目的端口&#xff08;各占2字节&#xff09; 二、序列号&#xff08;4字节&#xff09; 三、确认号&#xff08;4字节&#xff09; 四、数据偏移&#xff08;4位&#xff09; 五、保留位&#xff08;6位&#xff09; 六、六个控制位&#xff…

『pyqt5 从0基础开始项目实战』09.本地数据配置文件的保存与读取之txt类型(保姆级图文)

目录 导包和框架代码绑定按钮点击事件在dialog中编写代理配置弹窗UI和功能实现代理配置弹窗UI方法完整代码main.pythreads.pydialog.py 总结 欢迎关注 『pyqt5 从0基础开始项目实战』 专栏&#xff0c;持续更新中 欢迎关注 『pyqt5 从0基础开始项目实战』 专栏&#xff0c;持续…

Jenkins 实现自动化部署

安装 windows下安装&#xff1a;https://blog.csdn.net/u014641168/article/details/130286547 linux下安装&#xff1a;https://blog.csdn.net/u014641168/article/details/130282439 Jenkins支持JDK1.8对应版本说明&#xff1a;https://blog.csdn.net/u014641168/article/…