您现在的位置是:首页 > 编程 > 

【C++】优先级队列以及仿函数

2025-07-29 02:25:22
【C++】优先级队列以及仿函数 本篇我们来介绍一下优先级队列 priority_queue 。优先级队列的底层是数据结构中的堆,在C++中它是一个容器适配器,这个容器适配器比之前的栈和队列更复杂。1.priority_queue的介绍1.1 优先级队列的底层因为优先级队列就是堆,堆的底层是数组,所以优先级队列的默认适配器也是数组。 需要用到访问下标的,数组一定是最好的选择。不了解堆的,建议先看下面

【C++】优先级队列以及仿函数

本篇我们来介绍一下优先级队列 priority_queue 。优先级队列的底层是数据结构中的,在C++中它是一个容器适配器,这个容器适配器比之前的栈和队列更复杂。
1.priority_queue的介绍

1.1 优先级队列的底层

因为优先级队列就是堆,堆的底层是数组,所以优先级队列的默认适配器也是数组。

需要用到访问下标的,数组一定是最好的选择。不了解堆的,建议先看下面这篇博客。

数据结构中的堆:【数据结构】堆的概念、结构、模拟实现以及应用

1.2 文档介绍

文档介绍:priority_queue - C++ Reference

在C++中优先级队列的相关接口就是如上这些。这里的top,如果大的值优先级高,也就是大堆,top返回的就是堆里面的最大值,如果是小的数优先级高,也就是小堆,返回的就是最小值。

2.priority_queue的使用

使用优先级队列的时候需要包含头文件 #include <queue>

代码语言:javascript代码运行次数:0运行复制
	priority_queue<int> pq;
	pq.push(5);
	pq.push(2);
	pq.push(1);
	pq.push(10);
	pq.push(8);
	pq.push(4);

数据结构中的实现逻辑如下。 用到的是向上调整算法。

再通过top和pop的配合,把这些数打印出来。

代码语言:javascript代码运行次数:0运行复制
while (!())
{
	cout << () << " ";
	pq.pop();
}
cout << endl;

优先级队列默认大的值优先级高,在数据结构中的意思就是,默认为大堆。

如果想换成小的优先级高,就是小堆,我们就要传greeter

代码语言:javascript代码运行次数:0运行复制
priority_queue<int, vector<int>, greater<int>> pq;

改成greater,用同样的用例试试。

结果就是升序排列。

在数据结构中小堆就是要用到向下移调整,这里就不具体演示了,详情请看数据结构那篇博客。

.priority_queue的模拟实现

.1 做准备工作

建立一个头文件priority_queue.h,一个源文件,存放内容如下。

priority_queue.h 包含需要的头文件,所有实现用命名空间隔开。

代码语言:javascript代码运行次数:0运行复制
#pragma once
#include <iostream>
#include <vector>
using namespace std;
代码语言:javascript代码运行次数:0运行复制
namespace lyj
{
	template<class T, class Container = vector<T>>
	class priority_queue
	{
	public:

	private:
		Container _con;
	};
}

.2 父节点和子节点的对应关系

假设父节点在数组的下标为i: 左孩子在数组的下标:2*i + 1;右孩子在数组的下标:2*i + 2;

假设子节点在数组中的下标为j: 父节点在数组中的下标:(j - 1) / 2;

.2 插入数据

以大的数优先级高为例,在插入数据之前,这个堆一定已经是一个大堆了。这里的实现逻辑与数据结构中介绍的逻辑一样。

priority_queue.h 中实现。

.2.1 Swap交换

因为需要交换数据,所以我们写一个交换函数。

代码语言:javascript代码运行次数:0运行复制
void Swap(T& x1, T& x2)
{
	T tmp = x1;
	x1 = x2;
	x2 = tmp;
}
.2.2 AdjustUp向上调整

把向上调整的代码也重新封装。

代码语言:javascript代码运行次数:0运行复制
void AdjustUp(int child)
{
	int parent = (child - 1) / 2;
	while (child > 0 && _con[child] > _con[parent])
	{
		Swap(_con[child], _con[parent]);
		child = parent;
		parent = (child - 1) / 2;
	}
}

因为有默认的this指针,所以对比数据结构中C语言实现的堆的向上调整,现在我们只要传一个参数过去。

.. push插入

插入数据直接用_con的插入,插入完之后,要进行调整,保证这始终是个堆。

代码语言:javascript代码运行次数:0运行复制
void push(ct T& x)
{
	_con.push_back(x); //插入数据
	AdjustUp(_con.size() - 1);//向上调整
}

中测试一下。

代码语言:javascript代码运行次数:0运行复制
void test1()
{
	lyj::priority_queue<int> pq;
	pq.push(1);
	pq.push(2);
	pq.push();
	pq.push(4);
	pq.push(5);
}

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

.2 删除数据

在数据结构中介绍过,删除数据是删除堆顶数据。删除数据的逻辑是将收尾数据交换,交换后要删的数据在最后,然后再尾删,后用向下调整算法,将堆恢复。

priority_queue.h 中实现。

.2.1 向下调整

向下调整的逻辑和数据结构中一致。

代码语言:javascript代码运行次数:0运行复制
void AdjustDown(size_t parent)
{
	size_t child = parent * 2 + 1;
	while (child < _con.size())
	{
		if (child + 1 < _con.size() && _con[child] < _con[child + 1])
		{
			child++;
		}
		if (_con[child] > _con[parent])
		{
			Swap(_con[child], _con[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

参数我们只需要传一个parent就可以了。

.2.2 pop删除
代码语言:javascript代码运行次数:0运行复制
void pop()
{
	Swap(_con[0], _con[_con.size() - 1]);//首尾交换
	_con.pop_back();//删除
	AdjustDown(0); //向下调整
}

中测试一下。

代码语言:javascript代码运行次数:0运行复制
void test1()
{
	lyj::priority_queue<int> pq;
	pq.push(1);
	pq.push(2);
	pq.push();
	pq.push(4);
	pq.push(5);
	pq.pop();
	pq.pop();
}
.2. top、empty和size

priority_queue.h 中实现。

代码语言:javascript代码运行次数:0运行复制
ct T& top() 
{
	return _con[0];
}
代码语言:javascript代码运行次数:0运行复制
bool empty() ct
{
	return _();
}
代码语言:javascript代码运行次数:0运行复制
size_t size() ct
{
	return _con.size();
}

中测试一下。

代码语言:javascript代码运行次数:0运行复制
void test2()
{
	lyj::priority_queue<int> pq;
	pq.push(5);
	pq.push(2);
	pq.push(1);
	pq.push(10);
	while (!())
	{
		cout << () << " ";
		pq.pop();
	}
	cout << endl;
}
4.仿函数

我们前面说过,优先级队列默认为大堆,但是给第三个参数传greater过去,就可以改成小堆,这里说的less和greater就是仿函数。

目前我们只实现了一个大堆,要实现小堆就要把对比的逻辑全部改掉,但是库里面只需要传参数就可以了,如果我们想模拟实现的更接近库里面的优先级队列,就要先说说这个仿函数。

4.1 仿函数的介绍

仿函数本质是一个类,这个类重载了operator(),这个括号重载的是函数调用时参数列表的括号,它的对象可以像函数一样使用。

比如说我们现在要重载operator()实现两个数的比较。

代码语言:javascript代码运行次数:0运行复制
class Less
{
public:
	bool operator()(int x, int y)
	{
		return x < y;
	}
};

我们还可以改造一下,用类模板。

代码语言:javascript代码运行次数:0运行复制
template<class T>
class Less
{
public:
	bool operator()(ct T& x, ct T& y)
	{
		return x < y;
	}
};

这个仿函数的类没有成员变量,所以大小是1字节。

我们来使用一下。

代码语言:javascript代码运行次数:0运行复制
Less<int> lessF; //实例化出对象
cout << lessF(, 2) << endl; //对象的使用

我们只看 cout << lessF(, 2) << endl; 这句代码,会觉得我们是在使用一个函数,其实不是,这是一个类的对象,这个类的对象可以像函数一样使用。所以我们也把这个对象叫做函数对象。

完整的写法是下面这样的。运算符重载。

代码语言:javascript代码运行次数:0运行复制
cout << ()(1, 2) << endl;

我们还可以写一个Greater类,里面的operator()也是实现两个数的比较,比较逻辑和Less相反。

代码语言:javascript代码运行次数:0运行复制
template<class T>
class Greater
{
public:
	bool operator()(ct T& x, ct T& y)
	{
		return x > y;
	}
};

4.2 仿函数的简单应用

有哪些使用场景呢?比如说我们这里有个冒泡排序,现在默认排升序。

代码语言:javascript代码运行次数:0运行复制
void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		int flag = 0;
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j + 1] < a[j])
			{
				int tmp = a[j + 1];
				a[j + 1] = a[j];
				a[j] = tmp;
				flag = 1;
			}
		}
		if (flag == 0)
		{
			return;
		}
	}
}

我们想变成排降序,就要将代码 if (a[j + 1] < a[j]) 改成 if (a[j + 1] > a[j]) 。

现在我们添加一个模板参数Compare cmp。

代码语言:javascript代码运行次数:0运行复制
template<class Compare>
void BubbleSort(int* a, int n, Compare cmp)
{
	//...
}

用cmp来控制比较大小的逻辑。

代码语言:javascript代码运行次数:0运行复制
for (int j = 0; j < n - 1 - i; j++)
{
//	if (a[j] < a[j + 1]) 小于,排降序
	if (cmp(a[j], a[j + 1]))
	{
		int tmp = a[j + 1];
		a[j + 1] = a[j];
		a[j] = tmp;
		flag = 1;
	}
}

我们在调用这个BubbleSort的时候,通过传的第三个参数,就是我们的仿函数,来控制比较逻辑。

代码语言:javascript代码运行次数:0运行复制
int main()
{
	Less<int> lessF; //实例化
	Greater<int> greaterF;

	int a[] = { , 6, 2, 7, 8, 1, 9, 4 };
	BubbleSort(a, 8, lessF); //降序
	BubbleSort(a, 8, greaterF); //升序

	return 0;
}

这里其实是走了一个回调的过程。

在传参数的时候还可以用匿名对象。

代码语言:javascript代码运行次数:0运行复制
int a[] = { , 6, 2, 7, 8, 1, 9, 4 };
BubbleSort(a, 8, Less<int>()); //传匿名对象
BubbleSort(a, 8, Greater<int>()); //传匿名对象

这个用在优先级队列里是怎样的呢?

4. priority_queue用仿函数实现

首先我们把自己实现的两个仿函数Greater和Less放到priority_queue.h里。可以选择放在namespace里面也可以不放。

然后我们也要在priority_queue类模板多加一个模板参数Compare,并且给缺省值,默认大的数优先级高,就是大堆。

代码语言:javascript代码运行次数:0运行复制
template<class T, class Container = vector<T>, class Compare = Less<T>>

传参数的时候注意一下函数传参和类模板传参不一样。

函数参数要传匿名对象,类模板的参数要传类型。

因为我们传过来的是一个类型,所以要自己再定义一个对象。先改向上调整的代码。

代码语言:javascript代码运行次数:0运行复制
void AdjustUp(int child)
{
	Compare cmp; //定义对象
	int parent = (child - 1) / 2;
//	while (child > 0 && _con[parent] < _con[child])
	while (child > 0 && cmp(_con[parent], _con[child]))
	{
		Swap(_con[child], _con[parent]);
		child = parent;
		parent = (child - 1) / 2;
	}
}

再用同样的逻辑改一下向下调整。

代码语言:javascript代码运行次数:0运行复制
void AdjustDown(size_t parent)
{
	Compare cmp; //定义对象
	size_t child = parent * 2 + 1;
	while (child < _con.size())
	{
	//	if (child + 1 < _con.size() && _con[child] < _con[child + 1])
		if (child + 1 < _con.size() && cmp(_con[child], _con[child + 1]))
		{
			child++;
		}
	//	if (_con[parent] < _con[child])
		if (cmp(_con[parent], _con[child]))
		{
			Swap(_con[child], _con[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

只需要改动这两个函数,其余不变。

有了仿函数,我们就不用写两个类一个大堆一个小堆了。

中测试一下。

代码语言:javascript代码运行次数:0运行复制
void test2()
{
	lyj::priority_queue<int> pq;
	pq.push(5);
	pq.push(2);
	pq.push(1);
	pq.push(10);
	pq.push(6);
	pq.push();
	pq.push(8);

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

默认是大堆,然后结合top和pop按降序把数据打印出来。

再测试一下小堆,参数列表改一下就行。

现在就是小堆,然后结合top和pop按升序把数据打印出来。

现在我们优先级队列的模拟实现就算完成了,我们下篇再见~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2024-12-29,如有侵权请联系 cloudcommunity@tencent 删除函数数据c++队列对象

#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格

本文地址:http://www.dnpztj.cn/biancheng/1214126.html

相关标签:无
上传时间: 2025-07-25 05:34:37
留言与评论(共有 8 条评论)
本站网友 罗尼库尔曼
25分钟前 发表
原始发表:2024-12-29
本站网友 cd下载
21分钟前 发表
它的对象可以像函数一样使用
本站网友 网络短信群发平台
25分钟前 发表
用类模板
本站网友 经济适用住房管理办法
5分钟前 发表
ct T& y) { return x > y; } };4.2 仿函数的简单应用有哪些使用场景呢?比如说我们这里有个冒泡排序
本站网友 成功的英文
15分钟前 发表
本站网友 现在标准时间
14分钟前 发表
建议先看下面这篇博客
本站网友 机电设备有限公司
20分钟前 发表
greater<int>> pq;改成greater