C++STL---priority_queue知识总结及模拟实现

前言

和stack与queue一样,priority_queue也是一种容器适配器。

他的本质其实是堆,作优先级队列的底层需要能够通过随机迭代器访问,所以他的底层是可以由vector和queue实例化,默认情况下priority_queue默认是用vector作为底层实例化,此外我们还可以特指定deque作为他的底层进行实例化。

需要支持随即迭代器,是因为要始终维持堆,优先级队列的本质就是堆。容器适配器通过在需要时自动调用算法函数make_heap,push_heap,pop_heap来始终维持堆。

priority_queue默认是大堆。

priority_queue的使用

priority_queue定义方式

优先级队列有三个模板参数,第一个是元素种类,第二个是用来构造优先级队列的底层容器种类,第三个是构建的方式(大堆还是小堆)。

//这种是不指定底层容器和构造方式的构建,默认底层是vector,构建大堆
priority_queue<int> pq;

//这种指定了底层容器和构造方式,我这里设置的底层是deque<int>,构建的是小堆
priority_queue<int,deque<int>,greater<int>> pq1;

//我们这里如果要是想要选择构建大堆还是小堆的时候,就只能必须把底层容器的类型也给了
//这是设置缺省参数时的规定,这里不赘述

priority_queue的接口介绍

成员函数功能
push元素入队,通过push_heap函数使其移向堆中适当的位置
pop元素出队,通过pop_heap函数使剩下的元素维持住堆的形式
top获取堆顶元素(优先级最大或最小的元素)
size返回队中有效数据个数
empty判断是否为空
swap交换两个优先级队列中的内容

示例代码:

#include<queue>
#include<iostream>

using namespace std;

void test1()
{
	priority_queue<int> pq;
	pq.push(1);
	pq.push(2);
	pq.push(3);
	pq.push(4);
	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;
	priority_queue<int, vector<int>, greater<int>>pq2;
	pq2.push(1);
	pq2.push(2);
	pq2.push(3);
	pq2.push(4);
	while (!pq2.empty())
	{
		cout << pq2.top() << " ";
		pq2.pop();
	}
	cout << endl;
}

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

如何支持自定义类型使用priority_queue

1.使用仿函数

仿函数就是重载实现 ( ) ,使 ( ) 有实际的意义,因为使用的时候特别像函数调用,所以取名叫仿函数。例如我们有下面的一个类student,我们根据他的成绩_student从小到大进行排序:

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

class student
{
public:
	student(size_t id, size_t grade) :
		_id(id),
		_grade(grade)
	{}

	size_t getId() const
	{
		return _id;
	}

	size_t getGrade() const
	{
		return _grade;
	}
private:
	size_t _id;
	size_t _grade;
};

//先创建一个结构体cmp,struct访问权限是public
//里面构造仿函数,我们这里要实现从小到大排序,就要创建小堆,也就是greater的内容
struct cmp
{
	bool operator()(student& s1,student& s2) {
		return s1.getGrade() > s2.getGrade();
	}
};

void test1()
{
	student s1(1,100);
	student s2(2,80);
	student s3(3,90);
	student s4(4,60);
	vector<student>s;
	s.push_back(s1);
	s.push_back(s2);
	s.push_back(s3);
	s.push_back(s4);
	priority_queue<student, vector<student>, cmp> pq;
	for (auto& e : s)
	{
		pq.push(e);
	}
	while (!pq.empty())
	{
		student s = pq.top();
		cout << s.getId() << endl;
		pq.pop();
	}
}

int main()
{
	test1();
	return 0;
}
2.通过运算符重载来实现自定义实现比较函数

当你使用 priority_queue 时,它需要一个比较函数或者比较对象来决定元素的排序。这个比较函数需要满足一定的要求,特别是它需要能够处理 const 对象。因为priority_queue底层本质上是一个堆,在我们进行插入删除的时候,需要不断调整,在我们调整的过程中是不希望元素的数值改变的。因此在底层我们的编译器只接受const修饰的元素。所以我们在进行operator>或operator<的时候,里面的参数是一定要加上const的。

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

class student
{
public:
	friend bool operator>(student& s1, student& s2);
	student(size_t id, size_t grade) :
		_id(id),
		_grade(grade)
	{}

	size_t getId() const
	{
		return _id;
	}

	size_t getGrade() const
	{
		return _grade;
	}
private:
	size_t _id;
	size_t _grade;
};

//struct cmp
//{
//	bool operator()(student& s1, student& s2) {
//		return s1.getGrade() > s2.getGrade();
//	}
//};

bool operator>(const student& s1,const student& s2)//这里一定要加上const,只接收const对象
{
	return s1.getGrade() > s2.getGrade();
}

void test1()
{
	student s1(1, 100);
	student s2(2, 80);
	student s3(3, 90);
	student s4(4, 60);
	vector<student>s;
	s.push_back(s1);
	s.push_back(s2);
	s.push_back(s3);
	s.push_back(s4);
	priority_queue<student, vector<student>, greater<student>> pq;
	for (auto& e : s)
	{
		pq.push(e);
	}
	while (!pq.empty())
	{
		student s = pq.top();
		cout << s.getId() << endl;
		pq.pop();
	}
}

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

priority_queue的模拟实现

我们前面讲过优先级队列实际就是堆结构,我们要实现它就要先学习两个堆的算法:向上调整算法和向下调整算法。我们这里以构建大堆举例:

向上调整算法

void adjustUp(vector<int>& v,int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (v[parent] < v[child])
		{
			swap(v[parent], v[child]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//这里我们要构建的是一个大堆,如果儿子的值大于父亲的值就交换位置继续迭代向上迭代

向下调整算法

//堆的向下调整(大堆)
void adjustDown(vector<int>& v, int parent)
{
	//child记录左右孩子中值较大的孩子的下标
	int child = 2 * parent + 1;//先默认其左孩子的值较大
	while (child < v.size())
	{
		if (child + 1 < v.size() && v[child] < v[child + 1])//右孩子存在并且右孩子比左孩子还大
		{
			child++;//较大的孩子改为右孩子
		}
		if (v[parent] < v[child])//左右孩子中较大孩子的值比父结点还大
		{
			//将父结点与较小的子结点交换
			swap(v[child], v[parent]);
			//继续向下进行调整
			parent = child;
			child = 2 * parent + 1;
		}
		else//已成堆
		{
			break;
		}
	}
}

模拟实现

模拟实现实际上就是在上面两个调整算法的基础上,设计几个接口:

namespace cyf
{
	template<class T>
	struct less
	{
		bool operator()(const T& t1, const T& t2)
		{
			return t1 < t2;
		}
	};

	template<class T>
	struct greater
	{
		bool operator()(const T& t1, const T& t2)
		{
			return t1 > t2;
		}
	};

	template<class T,class Container=vector<T>,class Compare=less<int>>
	class Mypriority_queue
	{
	public:
		void AdjustUp(int child)
		{
			int parent = (child - 1) / 2;
			while (child != 0)
			{
				if (com_(con_[parent], con_[child]))
				{
					swap(con_[parent], con_[child]);
					child = parent;
					parent = (parent - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		void AdjustDown(int parent)
		{
			int child = 2 * parent + 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_[parent], con_[child]);
					parent = child;
					child = 2 * child + 1;
				}
				else
				{
					break;
				}
			}
		}

		void push(const T& val)//先尾插,再向上调整
		{
			con_.push_back(val);
			AdjustUp(con_.size() - 1);
		}

		int size()
		{
			return con_.size();
		}

        //先交换第一个元素和最后一个元素,之后将换后的最后一个元素删除,之后从0向下调整
		void pop()
		{
			swap(con_[0], con_[size() - 1]);
			con_.pop_back();
			AdjustDown(0);
		}

		T& top()//两种访问堆顶元素的top()函数
		{
			return con_[0];
		}

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

		bool empty() const
		{
			return con_.empty();
		}

	private:
		Container con_;//存储容器
		Compare com_;  //比较方式
	};
}

 以上就是有关优先级队列的全部内容,希望能对大家有所帮助!

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值