【C++】priority_queue(优先级队列)的使用和实现

文章目录

  • [1. 前言](#1. 前言)
  • [2. 使用](#2. 使用)
  • [3. 模拟实现](#3. 模拟实现)
    • [1. 铺垫](#1. 铺垫)
    • [2. push](#2. push)
    • [3. pop](#3. pop)
    • [4. top](#4. top)
    • [5. size](#5. size)
    • [6. empty](#6. empty)
    • [7. 仿函数](#7. 仿函数)
      • [1. 介绍](#1. 介绍)
      • [2. 通过仿函数改进优先级队列](#2. 通过仿函数改进优先级队列)
    • [8. 测试](#8. 测试)
  • [4. 源代码](#4. 源代码)
    • [1. priority_queue.h](#1. priority_queue.h)
    • [2. test.cpp](#2. test.cpp)

1. 前言

在前面我们已经了解了stack和queue的使用和模拟实现,我们也知道了他们是容器配置器,今天我们来介绍的priority_queue(优先级队列)也是一种容器配置器------>>>点击查看stack和queue的使用和实现

2. 使用

  1. 优先级队列其实就是我们数据结构中的堆,所以要求我们需要掌握堆之后再来看优先级队列------>>>点击查看【数据结构】堆,默认是大根堆,即第一个元素总是当前所有元素中最大的,这里是大根堆还是小根堆我们是通过仿函数来控制的,仿函数具体我们会在实现部分重点介绍的
  2. 这里的优先级队列依然是一种容器配置器,默认容器是vector
  3. 底层容器可以是任何标准容器类模板,也可以是其它特定设计的容器类。并且由于优先级队列的结构类似于堆,所以建堆以及调整的过程需要大量使用下标进行随机访问,所以这个底层容器类应该支持随机访问,并且支持以下操作:(push_back,尾部插入数据),(pop_back,尾部删除数据),(front,获取头部数据的引用),(size,获取元素的个数),(empty,判空)

3. 模拟实现

1. 铺垫

  1. 我们依然是采用模板来设计的,所以我们创建两个文件分别为priority_queue.h和test.cpp分别用于实现基本功能和测试
  2. 优先级队列也是容器配置器,默认容器是vector
cpp 复制代码
#pragma once

#include<iostream>
#include<vector>
#include<deque>
using namespace std;

namespace William
{
	template<class T, class Container = vector<T>>
	class priority_queue
	{
	public:
	private:
		Container _con;
	};
}

2. push

cpp 复制代码
void AdjustUp(size_t child)
{
	Compare com;
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		if (_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);
	AdjustUp(_con.size() - 1);
}
  1. push是为了在优先级队列中插入一个新元素并且保持当前指定堆的特性
  2. 这里的向上调整算法是我们在学习堆这个数据结构时重点分析过的,这里我们再稍微简单地说一下,我们这里默认实现的是大根堆,所以我们是希望大的数据在前面的,我们push时是在我们指定容器的最尾部插入一个新数据,然后通过向上调整算法继续维持大根堆的特性,如果孩子节点比父亲节点大就互换
  3. 剩下部分就是我们在介绍stack和queue时经常说的各种复用了,这里就不过多赘述了

3. pop

cpp 复制代码
void AdjustDown(size_t parent)
{
	Compare com;
	size_t child = parent * 2 + 1;
	while (child < _con.size())
	{
		if (child + 1 < _con.size() && _con[child] < _con[child + 1])
		{
			++child;
		}
		if (_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();
	AdjustDown(0);
}
  1. pop是为了弹出当前堆顶数据,并且让优先级队列中的其他数据保持指定堆的特性
  2. 这里的向下调整算法也是为了维持我们指定堆的特性,默认大根堆所以我们要让优先级队列维持大根堆的特性,这里我们的pop的逻辑也是在堆中详细介绍过的,简单说一下,我们的pop是要弹出堆顶数据的,我们是先让堆顶数据和指定容器最尾部的数据互换一下,然后弹出当前最尾部的数据即堆顶数据,然后对我们互换过的堆顶元素即原容器最尾部数据进行向下调整维持当前堆的特性

4. top

cpp 复制代码
const T& top() const
{
	return _con[0];
}
  1. top是为了返回当前堆顶数据的引用
  2. 这里我们即可以利用下标随机访问返回首元素也可以通过调用指定容器的front接口来返回首元素
  3. 为了对优先级队列中的数据进行封装,所以我们这里的引用加了const,那么既然的是const返回权限为只读,那么我们也可以使用const修饰this指针指向的对象,即让const对象和普通对象都可以进行调用

5. size

cpp 复制代码
size_t size() const
{
	return _con.size();
}
  1. size是为了获取元素的个数,所以我们直接调用传入容器的size接口就可以了
  2. 由于容器的大小不可能为负数,所以我们这里使用的是size_t类型
  3. 我们是这里针对size是不可能允许做任何修改的,所以我们这里直接使用const修饰this指针就可以了

6. empty

cpp 复制代码
bool empty() const
{
	return _con.size() == 0;
}
  1. empty就是来判空的,我们只需要调用对应容器的empty接口就可以了
  2. 这里我们也不做任何修改所以直接用const修饰this指针就可以

7. 仿函数

1. 介绍

  1. 仿函数其实就是一个类,这个类重载了operator(),使用这个类实例化出的对象可以像函数调用一样去使用
  2. 通常来讲,我们通常将这个仿函数的类定义为模板类,这样内置类型的对象可以进行比较,重载了小于操作符<或大于操作符>的自定义类型同样也可以使用仿函数实例化出的对象像函数调用一样去进行调用
cpp 复制代码
template<class T>
class Less
{
public:
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};

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

int main()
{
	Less com;
	int a = 10, b = 2;
	cout << com(a, b) << endl;// 调用起来看起来和调用函数一样
	cout << com.operator()(a, b) << endl;// 实际调用形式
	return 0;
}
  1. 用于比较的对象不一定是内置类型的,如果是自定义类型进行传值传参的消耗大,所以我们采用引用传参,并且这里仅仅是进行比较,不进行修改参数,所以函数的参数列表中的参数我们的类型我们设定为const T&
  2. 由于不对数据进行修改,所以this指针指向的对象我们加const进行修饰,这样const对象和普通对象都可以使用仿函数进行比较
  3. 上面我们自己实现的Less和Greater在库函数中是有的

2. 通过仿函数改进优先级队列

  1. 通过上面介绍的仿函数我们就可以改进我们的优先级队列了,利用仿函数我们就可以通过改变模板参数来实现控制大根堆还是小根堆了
cpp 复制代码
#pragma once

#include<iostream>
#include<vector>
#include<deque>
using namespace std;

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

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

namespace William
{
	template<class T, class Container = vector<T>, class Compare = Less<T>>// 默认是大堆
	class priority_queue
	{
	public:
	private:
		Container _con;
	};
}
  1. 我们只需要改变我们代码逻辑中的比较大小的逻辑就可以了,即改变我们的向上调整算法和向下调整算法
cpp 复制代码
void AdjustUp(size_t child)
{
	Compare com;
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		// if (_con[parent] < _con[child])
		if (com(_con[parent], _con[child]))
		{
			swap(_con[child], _con[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void AdjustDown(size_t parent)
{
	Compare com;
	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() && com(_con[child], _con[child + 1]))
		{
			++child;
		}
		// if (_con[parent] < _con[child])
		if (com(_con[parent], _con[child]))
		{
			swap(_con[child], _con[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

8. 测试

cpp 复制代码
#include "priority_queue.h"

int main()
{
	William::priority_queue<int, vector<int>, Greater<int>> pq;
	pq.push(3);
	pq.push(9);
	pq.push(6);
	pq.push(2);
	pq.push(1);
	pq.push(5);
	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;
	return 0;
}

4. 源代码

1. priority_queue.h

cpp 复制代码
#pragma once

#include<iostream>
#include<vector>
#include<deque>
using namespace std;

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

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

namespace William
{
	template<class T, class Container = vector<T>, class Compare = Less<T>>// 默认是大堆
	class priority_queue
	{
	public:

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

		void AdjustDown(size_t parent)
		{
			Compare com;
			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() && com(_con[child], _con[child + 1]))
				{
					++child;
				}
				// if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

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

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

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

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

		bool empty() const
		{
			return _con.size() == 0;
		}
	private:
		Container _con;
	};
}

2. test.cpp

cpp 复制代码
#include "priority_queue.h"

int main()
{
	William::priority_queue<int, vector<int>, Greater<int>> pq;
	pq.push(3);
	pq.push(9);
	pq.push(6);
	pq.push(2);
	pq.push(1);
	pq.push(5);
	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;
	return 0;
}
相关推荐
代码中介商1 小时前
C++ STL入门:vector与字符串流详解
开发语言·c++
fqbqrr1 小时前
2605C++,C++类的继承1
c++
王老师青少年编程1 小时前
csp信奥赛C++高频考点专项训练之字符串 --【字符串排序】:[NOIP 1998 提高组] 拼数
c++·字符串·csp·高频考点·信奥赛·拼数·字符串排序
草莓熊Lotso1 小时前
【Linux网络】从 0 到 1 实现高性能 UDP 聊天室:深入拆解 Linux 网络编程与线程池架构
linux·运维·服务器·网络·数据库·c++·udp
basketball6161 小时前
C++ iomanip 常用函数
开发语言·c++
sanqima1 小时前
C++里strcpy()拷贝的3种写法
c++·字符串拷贝
艾莉丝努力练剑1 小时前
【Linux网络】Linux 网络编程:应用层自定义协议与序列化(2)序列化与反序列化
linux·运维·服务器·c++·网络协议·序列化
智者知已应修善业1 小时前
【51单片机一个按键切合初始流水灯按一下对半闪烁按一下显示时间】2023-10-16
c++·经验分享·笔记·算法·51单片机
雪度娃娃2 小时前
结构型设计模式——外观模式
c++·设计模式·外观模式