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

💗个人主页💗

⭐个人专栏------C++学习

💫点击关注🤩一起学习C语言💯💫

目录

导读

一、什么是优先队列

二、优先队列的使用

[1. 优先队列的构造](#1. 优先队列的构造)

[2. 优先队列的基本操作](#2. 优先队列的基本操作)

[3. 使用示例](#3. 使用示例)

三、优先队列模拟实现

[1. 仿函数](#1. 仿函数)

[2. 成员变量](#2. 成员变量)

[3. 向上调整](#3. 向上调整)

[4. push函数](#4. push函数)

[5. 向下调整](#5. 向下调整)

[6. pop函数](#6. pop函数)

[7. empty和size](#7. empty和size)

[8. top函数](#8. top函数)

四、完整代码

[1. p_queue.h](#1. p_queue.h)

[2. test.cpp](#2. test.cpp)


导读

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

一、什么是优先队列

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

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

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

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

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

特点:

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

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

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

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

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

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

二、优先队列的使用

1. 优先队列的构造

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

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

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

cpp 复制代码
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>头文件
cpp 复制代码
#include <queue>
  1. 定义优先队列:使用priority_queue类定义一个优先队列对象(默认大堆)
cpp 复制代码
priority_queue<int> pq;
  1. 插入元素:使用push()函数向优先队列中插入元素
cpp 复制代码
pq.push(5);  // 插入元素5
pq.push(2);  // 插入元素2
pq.push(8);  // 插入元素8
  1. 访问元素:可以使用top()函数获取具有最高优先级的元素
cpp 复制代码
int highestPriority = pq.top();  // 获取最高优先级元素
  1. 删除元素:使用pop()函数从优先队列中删除具有最高优先级的元素
cpp 复制代码
pq.pop();  // 删除最高优先级元素

完整示例:

cpp 复制代码
#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等),从而实现对容器中的元素进行自定义的操作和排序。

cpp 复制代码
	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(),用于比较两个元素的大小。它接受两个常引用参数 xy,并返回一个布尔值。在该实现中,它通过 < 运算符来比较 xy 的大小,如果 x 小于 y,则返回 true,否则返回 false

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

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

2. 成员变量

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

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

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

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

cpp 复制代码
    template<class T, class Container = vector<T>, class Compare = less<T>>
	class priority_queue
	{
	
	private:
		Container _con;
	};

3. 向上调整

cpp 复制代码
		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 计算出其父节点的索引,并使用循环进行调整。

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

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

4. push函数

cpp 复制代码
		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. 向下调整

cpp 复制代码
		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函数

cpp 复制代码
		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

cpp 复制代码
		bool empty()
		{
			return _con.empty();
		}

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

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

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

8. top函数

cpp 复制代码
		const T& top()
		{
			return _con[0];
		}

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

四、完整代码

1. p_queue.h

cpp 复制代码
#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

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;
}
相关推荐
凡人的AI工具箱12 分钟前
40分钟学 Go 语言高并发:Pipeline模式(一)
开发语言·后端·缓存·架构·golang
微澜-12 分钟前
编译以前项目更改在x64下面时报错:函数“PVOID GetCurrentFiber(void)”已有主体
c++
YuanLiu_22718 分钟前
代码随想录算法训练营第十三天(递归遍历;迭代遍历;统一迭代;层序遍历)
java·数据结构·笔记·算法·leetcode
闻缺陷则喜何志丹20 分钟前
【C++动态规划】1411. 给 N x 3 网格图涂色的方案数|1844
c++·算法·动态规划·力扣·网格·数目·涂色
achaoyang21 分钟前
【Python中while循环】
开发语言·python
呆呆小雅23 分钟前
C# 封装
java·开发语言·c#
蒜蓉大猩猩30 分钟前
Vue.js - 组件化编程
开发语言·前端·javascript·vue.js·前端框架·ecmascript
仙俊红36 分钟前
快速运行openMMOCR
深度学习·算法
南鸳61043 分钟前
Scala:根据身份证号码,输出这个人的籍贯
开发语言·后端·scala
-Max-静-1 小时前
Paddle Inference部署推理(十八)
人工智能·windows·深度学习·算法·paddle·推理 部署