【C++】优先队列的使用及模拟实现

news2025/2/7 20:10:15

💗个人主页💗
⭐个人专栏——C++学习⭐
💫点击关注🤩一起学习C语言💯💫

目录

导读

一、什么是优先队列

二、优先队列的使用

1. 优先队列的构造

2. 优先队列的基本操作

3. 使用示例

三、优先队列模拟实现

1. 仿函数

2. 成员变量

3. 向上调整

4. push函数

5. 向下调整

6. pop函数

7. empty和size

8. top函数

四、完整代码

1. p_queue.h

2. test.cpp


导读

我们上次学习了栈和队列,今天我们来学习优先队列,主要是了解它的一些基本使用和如何模拟实现。

一、什么是优先队列

优先队列(Priority Queue)是一种高效的数据结构,它是队列的一种扩展,不同之处在于每个元素都有一个相关的优先级。在优先队列中,元素不是按照插入的顺序进行排列,而是按照元素的优先级进行排列,优先级高的元素排在前面。

优先队列的定义可以有多种方式,其中一种常见的定义方式是基于堆(Heap)数据结构实现的。堆是一种二叉树,满足以下两个条件:

  1. 堆的根节点是最小或最大元素,即满足最小堆或最大堆的性质。
  2. 堆的每个节点的值都小于或大于其子节点的值,即满足堆的有序性。

基于堆实现的优先队列可以使用数组或链表来表示堆结构,并提供一些基本的操作,如插入元素、删除优先级最高的元素等。插入元素时,根据元素的优先级,将元素插入到合适的位置;删除元素时,取出优先级最高的元素,并保持堆的有序性。

不同于普通队列的FIFO(先进先出)特性,优先队列的元素按照优先级进行排序,具有最高优先级的元素会被最先处理。

特点:

  1. 元素具有优先级:与普通队列不同,优先队列中的元素具有优先级。每个元素都被赋予一个优先级值,用来确定其在队列中的位置。

  2. 按优先级排序:优先队列中的元素按照优先级进行排序。具有最高优先级的元素会被放在队列的最前面。

  3. 自动排序:在插入元素时,优先队列会自动根据元素的优先级进行排序。较高优先级的元素会被排在前面,较低优先级的元素会被排在后面。

  4. 快速访问最高优先级元素:优先队列支持快速访问具有最高优先级的元素。可以通过获取队列的顶部元素来获得具有最高优先级的元素。

  5. 插入和删除操作的时间复杂度:在堆实现的优先队列中,插入和删除元素的平均时间复杂度为O(log n),其中n是当前队列中的元素个数。

需要注意的是,优先队列并不保证相同优先级的元素的顺序,它只保证具有较高优先级的元素会被优先处理。如果需要保持相同优先级元素的顺序,可以通过自定义比较函数来实现。

二、优先队列的使用

1. 优先队列的构造

默认情况下,priority_queue使用less<int>作为比较器,以使元素按降序排列。也就是大堆。

如果要使用其他比较器或自定义排序规则,可以在创建优先队列对象时传递一个比较器函数对象或lambda表达式。

例如,要使元素按升序排列,可以使用以下代码:

priority_queue<int, vector<int>, greater<int>> pq;

优先队列的构造方式有以下几种:

  1. 默认构造:使用无参构造函数创建一个空的优先队列,例如:priority_queue<int> pq;

  2. 指定容器构造:可以通过指定容器类型和容器对象来构造优先队列。例如,使用vector容器构造一个最大堆优先队列:priority_queue<int, vector<int>> pq;,使用现有的vector对象构造优先队列:vector<int> vec; priority_queue<int, vector<int>> pq(vec);

  3. 指定比较函数构造:可以通过指定一个自定义的比较函数来构造优先队列,以改变默认的元素优先级比较规则。比较函数可以是自定义的函数、函数指针或者函数对象。例如,构造一个从小到大排序的优先队列:priority_queue<int, vector<int>, greater<int>> pq;,构造一个使用自定义比较函数的优先队列:auto cmp = [](int a, int b){ return a % 10 < b % 10; }; priority_queue<int, vector<int>, decltype(cmp)> pq(cmp);

2. 优先队列的基本操作

优先队列的基本操作包括插入元素、删除最高优先级元素、判断队列是否为空等。常用的操作函数有:

  • push(element):将元素插入到优先队列中。
  • pop():移除优先队列中的最高优先级元素。
  • top():返回优先队列中的最高优先级元素。
  • empty():判断优先队列是否为空,如果为空返回true,否则返回false
  • size():返回优先队列中元素的个数。

3. 使用示例

1. 包含头文件:首先需要包含<queue>头文件

#include <queue>

2. 定义优先队列:使用priority_queue类定义一个优先队列对象(默认大堆)

priority_queue<int> pq;

 3. 插入元素:使用push()函数向优先队列中插入元素

pq.push(5);  // 插入元素5
pq.push(2);  // 插入元素2
pq.push(8);  // 插入元素8

4. 访问元素:可以使用top()函数获取具有最高优先级的元素

int highestPriority = pq.top();  // 获取最高优先级元素

 5. 删除元素:使用pop()函数从优先队列中删除具有最高优先级的元素

pq.pop();  // 删除最高优先级元素

完整示例: 

#include <iostream>
#include <queue>
using namespace std;

int main() 
{
    priority_queue<int> pq;

    pq.push(5);
    pq.push(2);
    pq.push(8);

    int highestPriority = pq.top();
    cout << "The highest priority element is: " << highestPriority << endl;

    pq.pop();

    highestPriority = pq.top();
    cout << "The new highest priority element is: " << highestPriority << endl;

    return 0;
}

三、优先队列模拟实现

1. 仿函数

因为我们下面的模拟实现要用到这个知识,所以我们先来了解一下。

仿函数(Functor)是一种重载了圆括号运算符 operator() 的类对象,使其具有函数的行为。通过重载圆括号运算符,我们可以像调用函数一样使用这个类对象来执行某些操作。

仿函数可以将函数调用的语义封装在类对象的操作中,从而具有更多的灵活性和可定制性。它可以接受参数,执行特定的操作,并返回一个结果。

使用仿函数的好处是可以将其作为参数传递给其他函数或容器类(如sorttransformfind_if等),从而实现对容器中的元素进行自定义的操作和排序。

	template<class T>
	class less
	{
	public:
		bool operator()(const T& x, const T& y)
		{
			return x < y;
		}
	};

	template<class T>
	class greater
	{
	public:
		bool operator()(const T& x, const T& y)
		{
			return x > y;
		}
	};
  1. less 类实现了一个比较操作符函数 operator(),用于比较两个元素的大小。它接受两个常引用参数 x 和 y,并返回一个布尔值。在该实现中,它通过 < 运算符来比较 x 和 y 的大小,如果 x 小于 y,则返回 true,否则返回 false

  2. greater 类与 less 类类似,也实现了一个比较操作符函数 operator(),用于比较两个元素的大小。同样,它接受两个常引用参数 x 和 y,并返回一个布尔值。不同的是,它通过 > 运算符来比较 x 和 y 的大小,如果 x 大于 y,则返回 true,否则返回 false

这两个仿函数类可以在定义优先队列时作为比较准则的类型参数进行使用,用于指定元素之间的比较规则。

2. 成员变量

下述代码是在C++中定义优先队列(priority_queue)的模板类。优先队列是一种基于堆(heap)的数据结构,它的特点是每次取出的元素都是当前优先级最高的元素。

在模板类的定义中,有三个模板参数:

  1. T:表示元素的类型。
  2. Container:表示底层容器的类型,默认为vector<T>。优先队列使用底层容器来存储元素,可以根据需要选择不同类型的容器。
  3. Compare:表示元素之间的比较准则,默认为less<T>。比较准则是一个仿函数(应用上述提到的仿函数的概念),它定义了如何比较两个元素的优先级。默认情况下,使用less<T>来进行比较,即优先级高的元素被认为是小的元素。

在模板类的私有部分,定义了一个私有变量_con,它是容器类型Container的一个对象,用于存储优先队列的元素。

    template<class T, class Container = vector<T>, class Compare = less<T>>
	class priority_queue
	{
	
	private:
		Container _con;
	};

3. 向上调整

		void adjust_up(size_t child)
		{
			Compare com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

上述代码是一个成员函数 adjust_up 的实现,用于调整优先队列中某个节点的位置以保持堆的性质。

在该函数中,首先创建一个 Compare 对象 com,用于进行元素的比较。

然后,根据子节点的索引 child 计算出其父节点的索引,并使用循环进行调整。

在每次循环中,比较父节点和子节点的值,如果父节点的值比子节点的值小(通过调用 com 的 operator() 来比较),则交换两者的位置,并更新子节点和父节点的索引。这样,每次循环都会将较大的值上移一层,直到子节点的值不大于父节点的值或者子节点已经到达根节点。

最终,该函数保证经过调整后,优先队列中的元素按照 Compare 对象 com 所定义的比较准则(通过调用 com 的 operator())保持了堆的性质。

4. push函数

		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}

首先,将元素 x 插入到容器 _con 的末尾,使用 push_back 函数。

然后,调用 adjust_up 函数对插入的元素进行上调操作。_con.size() - 1 表示插入元素的索引,即最后一个元素的索引。

adjust_up 函数的作用是将插入的元素与其父节点进行比较并交换,以保持堆的性质。

通过这样的操作,插入的元素将逐步上移,直到满足堆的性质为止。

5. 向下调整

		void adjust_down(size_t parent)
		{
			Compare com;
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					++child;
				}

				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

以上代码是一个成员函数 adjust_down 的实现,用于在删除元素或进行堆化操作时,将某个节点与其子节点进行比较并交换,以保持堆的性质。

函数接收一个参数 parent,表示要进行调整的节点的索引。

首先,创建一个 Compare 对象 com,用于指定比较准则。

然后,根据节点的索引 parent 计算其左子节点的索引 child,即 child = parent * 2 + 1

接下来,使用一个循环,不断比较父节点和子节点的值,如果满足交换条件,则进行交换操作。

具体的交换条件如下:

  1. 如果右子节点存在且右子节点的值大于左子节点的值,将 child 增加 1,即指向右子节点。
  2. 如果父节点的值小于子节点的值,进行交换操作,即将父节点和子节点的值互换。
  3. 更新父节点和子节点的索引,父节点变为交换后的子节点,子节点变为新的左子节点。

最后,如果不满足交换条件,则退出循环。

通过这样的操作,被调整的节点将逐渐下移,直到满足堆的性质为止。

6. pop函数

		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjust_down(0);
		}

首先,使用 swap 函数将堆顶元素 _con[0] 与最后一个元素 _con[_con.size() - 1] 进行交换。

然后,使用 pop_back 函数将最后一个元素删除。

最后,调用 adjust_down 函数对堆顶元素进行调整,以保持堆的性质。

7. empty和size

		bool empty()
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}

empty 函数用于判断堆是否为空,通过调用 _con.empty() 来判断内部容器 _con 是否为空,如果为空则返回 true,否则返回 false

size 函数用于返回堆中元素的个数,通过调用 _con.size() 来获取内部容器 _con 的大小,即堆中元素的个数,并将其返回。

8. top函数

		const T& top()
		{
			return _con[0];
		}

top 函数用于返回堆顶的元素,即堆中最大(或最小)的元素。在该实现中,直接通过 _con[0] 获取堆顶的元素,并将其返回。注意此处返回的是一个常引用 const T&,表示返回的元素不能被修改。

四、完整代码

1. p_queue.h

#pragma once
#include <iostream>

#include <vector>
#include<algorithm>
using namespace std;

namespace Myq_queue
{
	template<class T>
	class less
	{
	public:
		bool operator()(const T& x, const T& y)
		{
			return x < y;
		}
	};

	template<class T>
	class greater
	{
	public:
		bool operator()(const T& x, const T& y)
		{
			return x > y;
		}
	};

	template<class T, class Container = vector<T>, class Compare = less<T>>
	class priority_queue
	{
	public:
		void adjust_up(size_t child)
		{
			Compare com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}

		void adjust_down(size_t parent)
		{
			Compare com;
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					++child;
				}

				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjust_down(0);
		}

		bool empty()
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}

		const T& top()
		{
			return _con[0];
		}
	private:
		Container _con;
	};
}

2. test.cpp

void test_priority_queue()
{

	// 小堆
	Myq_queue::priority_queue<int, vector<int>, Myq_queue::greater<int>> pq;
	pq.push(2);
	pq.push(1);
	pq.push(4);
	pq.push(3);
	pq.push(7);
	pq.push(8);

	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;

}
int main()
{
	test_priority_queue();
	return 0;
}

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

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

相关文章

C++初学者指南第一步---12.引用

C初学者指南第一步—12.引用 文章目录 C初学者指南第一步---12.引用1. 功能&#xff08;和限制&#xff09;1.1 非常量引用1.2 常量引用1.3 auto引用 2.用法2.1 范围for循环中的引用2.2 常量引用的函数形参2.3 非常量引用的函数形参2.4 函数参数的选择&#xff1a;copy / const…

emqx5.6.1 数据、配置备份与迁移

EMQX 支持导入和导出的数据包括&#xff1a; EMQX 配置重写的内容&#xff1a; 认证与授权配置规则、连接器与 Sink/Source监听器、网关配置其他 EMQX 配置内置数据库 (Mnesia) 的数据 Dashboard 用户和 REST API 密钥客户端认证凭证&#xff08;内置数据库密码认证、增强认证…

cas客户端流程详解(源码解析)--单点登录

博主之前一直使用了cas客户端进行用户的单点登录操作&#xff0c;决定进行源码分析来看cas的整个流程&#xff0c;以便以后出现了问题还不知道是什么原因导致的 cas主要的形式就是通过过滤器的形式来实现的&#xff0c;来&#xff0c;贴上示例配置&#xff1a; 1 <list…

海南聚广众达电子商务咨询有限公司抖音电商新引擎

在数字化浪潮席卷而来的今天&#xff0c;抖音电商作为新兴的商业模式&#xff0c;正以其独特的魅力和无限的潜力&#xff0c;引领着电子商务行业的革新与发展。海南聚广众达电子商务咨询有限公司&#xff0c;作为专注于抖音电商服务的领军企业&#xff0c;凭借其专业的团队、丰…

双例集合(三)——双例集合的实现类之TreeMap容器类

Map接口有两个实现类&#xff0c;一个是HashMap容器类&#xff0c;另一个是TreeMap容器类。TreeMap容器类的使用在API上于HashMap容器类没有太大的区别。它们的区别主要体现在两个方面&#xff0c;一个是底层实现方式上&#xff0c;HashMap是基于Hash算法来实现的吗&#xff0c…

【C语言】函数指针数组和指向函数指针数组的指针

1 函数指针数组 数组是一个存放相同类型数据的存储空间&#xff0c;那我们已经学习了指针数组。 比如&#xff1a; int *arr[10];//数组的每个元素是int* 那要把函数的地址存到一个数组中&#xff0c;那这个数组就叫函数指针数组&#xff0c;那函数指针的数组如何定义呢&am…

OS复习笔记ch11-2

上一节我们学习的内容是I/O系统的特点和设备分类和差异&#xff0c;这一节我们将主要关注I/O控制方式、OS设计问题、I/O逻辑结构等。 I/O功能的演变 在专栏的ch1-2中&#xff0c;我们详细讲解了CPU与外设的三种交互方式&#xff0c;这里简单地带过。 &#xff08;1&#xff0…

C++之STL(一)

1、泛型程序设计 目的&#xff1a;提供相同的算法&#xff0c;相同的逻辑&#xff0c;来对不同类型的数据结构进行操作。 所以需要将类型当作参数&#xff0c;也就是参数类型化。 2、什么是STL STL是基于模板实现的。编译的时候进行实例化 3、STL组件 4、容器算法迭代器关系 …

第二次IAG

IAG in NanJing City 我与南京奥体的初次相遇&#xff0c;也可能是最后一次&#xff01; 对我来说,IAG 演唱会圆满结束啦! 做了两场充满爱[em]e400624[/em]的美梦 3.30号合肥站&#xff0c;6.21号南京站[em]e400947[/em] 其实&#xff0c;没想到昨天回去看呀!(lack of money […

如何修改外接移动硬盘的区号

- 问题介绍 当电脑自身内存不够使用的时候&#xff0c;使用外接硬盘扩展内存是一个不错的选择。但是当使用的外接硬盘数量过多的时候&#xff0c;会出现分配硬盘的区号变动的情况&#xff0c;这种情况下会极大的影响使用的体验情况。可以通过以下步骤手动调整恢复 - 配置 版本…

SpringBoot 快速入门(保姆级详细教程)

目录 一、Springboot简介 二、SpringBoot 优点&#xff1a; 三、快速入门 1、新建工程 方式2&#xff1a;使用Spring Initializr创建项目 写在前面&#xff1a; SpringBoot 是 Spring家族中的一个全新框架&#xff0c;用来简化spring程序的创建和开发过程。SpringBoot化繁…

【C语言】操作符(上)

目录 1. 操作符的分类 2. 原码、反码、补码 3. 移位操作符 3.1 左移操作符 3.2 右移操作符 4. 位操作符&#xff1a;&、|、^、~ 5. 单目操作符 6. 逗号表达式 最近准备期末考试&#xff0c;好久不见啦&#xff0c;现在回归—— 正文开始—— 1. …

WPF文本绑定显示格式StringFormat设置-数值类型处理

绑定显示格式设置 在Textblock等文本控件中&#xff0c;我们经常要绑定一些数据类型&#xff0c;但是我们希望显示的时候能够按照我们想要的格式去显示&#xff0c;比如增加文本前缀&#xff0c;后面加单位&#xff0c;显示百分号等等&#xff0c;这种就需要对绑定格式进行处理…

【Java】已解决java.io.ObjectStreamException异常

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决java.io.ObjectStreamException异常 在Java中&#xff0c;java.io.ObjectStreamException是一个在序列化或反序列化对象时可能抛出的异常基类。这个异常通常表示在对象流处理…

iMazing3软件下载-详细安装教程视频

​值得肯定的是智能备份&#xff1a;iMazing为使用者提供了免费的备份服务&#xff0c;并且支持两种连接方式&#xff1a;USB数据线连接备份和Wi-Fi无线连接&#xff0c;所备份的文件不会被覆盖。我们必须承认iMazing软件特色&#xff1a;使用你的 iOS 设备像外部驱动器。基本上…

MSPM0G3507——特殊的串口0

在烧录器中有串口0&#xff0c;默认也是串口0通过烧录线给电脑发数据。 如果要改变&#xff0c;需要变一下LP上的跳线帽。 需要更改如下位置的跳线帽

SpringBoot 搭建sftp服务 实现远程上传和下载文件

maven依赖&#xff1a; <dependency><groupId>com.jcraft</groupId><artifactId>jsch</artifactId><version>0.1.55</version> </dependency>application.yml sftp:protocol: sftphost: port: 22username: rootpassword: sp…

论文阅读03(基于人类偏好微调语言模型)

1.主题 基于人类偏好微调语言模型&#xff08;Fine-Tuning Language Models from Human Preferences&#xff09; 出处&#xff1a; Fine-Tuning Language Models from Human Preferences、 2.摘要 奖励学习使得强化学习&#xff08;RL&#xff09;可以应用于那些通过人类判断…

云安全下的等级保护2.0解决方案

云安全解决方案 知识星球&#x1f517;除了包含技术干货&#xff1a;Java代码审计、web安全、应急响应等&#xff0c;还包含了安全中常见的售前护网案例、售前方案、ppt等&#xff0c;同时也有面向学生的网络安全面试、护网面试等。 ​

Git简单使用和理解

workspace: 本地的工作目录。 index/stage&#xff1a;暂存区域&#xff0c;临时保存本地改动。 local repository: 本地仓库&#xff0c;只想最后一次提交HEAD。 remote repository&#xff1a;远程仓库。 对于Git,首先应该明白第一git是一种分布式版本控制系统&#xff0c;最…