首页 > 编程语言 >C++ STL - 优先级队列及其模拟实现

C++ STL - 优先级队列及其模拟实现

时间:2024-03-27 20:03:12浏览次数:36  
标签:优先级 parent STL C++ return child const size con

目录

0. 引言

1. priority_queue 介绍 

1.1 构造函数 

1.2 priority_queue 接口函数使用 

1.3 仿函数 

 1.4 题目练习

 2. priority_queue 模拟实现

2.1基本框架:

2.2 默认构造函数

2.3 基本函数

2.4 堆的向上以及向下调整


0. 引言

优先队列 (priority_queue) 是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。优先队列和堆本质是一样的,以数组形式存储的完全二叉树。

1. priority_queue 介绍 

1.1 构造函数 

我们可以看到有两种构造方式,一个是构造一个空对象,另一个是通过迭代器的区间来构造,默认的构造出的是大堆。

priority_queue<int> pq1; //直接构造空对象

接下来我们分别以大堆和小堆的方式来构造对象:

	vector<int> v1 = {3,2,7,6,0,4,1,9,8,5};
	priority_queue<int, vector<int>, less<int>> pq1(v1.begin(), v1.end());
                                    //less-大堆
	while (!pq1.empty())
	{
		cout << pq1.top() << " ";
		pq1.pop();
	}
	cout << endl;

	priority_queue<int, vector<int>, greater<int>> pq2(v1.begin(), v1.end());
                                    //greater-小堆
	while (!pq2.empty())
	{
		cout << pq2.top() << " ";
		pq2.pop();
	}
	cout << endl;

	priority_queue<int> pq3(v1.begin(), v1.end());
                                    //默认大堆
	while (!pq3.empty())
	{
		cout << pq3.top() << " ";
		pq3.pop();
	}
	cout << endl;

因此我们得出: less - 大堆, greater - 小堆。 

1.2 priority_queue 接口函数使用 

接口函数主要包括以下:

函数说明
empty检测优先级队列是否为空,是返回true,否则返回 false
top返回优先级队列中最大(最小元素),即堆顶元
push在优先级队列中插入元素x
pop删除优先级队列中最大(最小)元素,即堆顶元素

1.3 仿函数 

仿函数又名函数对象 function objects 仿函数的主要作用是借助类和运算符重载,做到同一格式兼容所有函数。由于模板将 less 用作大堆,而 greater 用做小堆,是在有点别扭,如果是我们自己实现仿函数的化,肯定会按照习惯来写,less 表示小堆,greater 表示大堆。例如:

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

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

 1.4 题目练习

优先级队列适合来进行TOPK 以及 排序问题,因为其底层是和堆一模一样的。现在我们一起来看下面这道题:

这题如果不关心时间复杂度,直接利用 sort 排序将会很简单:

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        sort(nums.begin(),nums.end());
        return nums[nums.size()-k];    
    }
};

 当我们使用优先级队列时,时间复杂度会更好:

//大堆
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
    priority_queue<int> pq1(nums.begin(), nums.end());
      for(int i = 0;i < k-1; i++)
      {
            pq1.pop();
      }
       return pq1.top(); 
    }
};
//小堆
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
    priority_queue<int,vector<int>, greater<int>> pq1(nums.begin(), nums.begin()+k);
    for(int i = k ;i < nums.size(); i++)
    {
        if(nums[i] > pq1.top())
        {
            pq1.pop();
            pq1.push(nums[i]);
        }
    }
    return pq1.top(); 
    }  
};

 2. priority_queue 模拟实现

优先级队列的模拟实现,难点在于堆的向上和向下调整。我们首先展现整体代码,然后再逐一分进行分析。

2.1 整体代码

我们单独创建了一个 PriorityQueue.h 文件。

#pragma once
#include<vector>
#include<iostream>
using namespace std;
namespace LHY
{
	template<class T, class Container = vector<T>>
	class priority_queue
	{
	public:
		void adjust_up(int child)
		{
			int 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 adjust_down(int parent)
		{
			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 push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}

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

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

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

		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};

从中我们可以看到私有成员变量为底层容器对象。即 Container _con; class Container = vector<T> 。

2.2 基本函数

基本函数为:

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

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

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

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

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

我们可以清楚的看到,优先级队列的插入和删除,返回大小以及判空直接调用底层容器接口即可。而因为需要满足堆的性质,push 和 pop 则需要向上和向下调整堆。此处我们默认建立的是大堆,当push一个数比其父节点大,则需要交换位置,即向上调整。当 pop 操作则是先首尾交换,再删除尾元素,此时不满足堆条件,顶部元素则需要向下调整。如下图所示:

2.3 模拟实现 

void test_priority_queue()
{
	priority_queue<int> pq;
	pq.push(1);
	pq.push(0);
	pq.push(5);
	pq.push(2);
	pq.push(1);
	pq.push(7);

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

2.4 仿函数切换大小堆 

我们在 1.3 介绍了仿函数及其实现,那么我们能不能应用到上面的代码中呢?答案是当然可以。

代码如下:

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

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

	template<class T, class Container = vector<T>,class Comapre = less<T>>
	class priority_queue
	{
	public:
		void adjust_up(int child)
		{
			Comapre com;
			int 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 adjust_down(int parent)
		{
			size_t child = parent * 2 + 1;
			Comapre com;
			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);
			adjust_up(_con.size() - 1);
		}

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

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

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

		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};

    template<class T, class Container = vector<T>,class Comapre = less<T>>

   if (com(_con[parent], _con[child]))等等的变化使得我们可以改变大小堆。例如:

2.5 日期类 

假设我们现在的数据不是 vector<int> 而是 日期类对象,会发生什么情况呢?我们先看日期类代码:

class Date
	{
	public:
		Date(int year = 1900, int month = 1, int day = 1)
			: _year(year)
			, _month(month)
			, _day(day)
		{}

		bool operator<(const Date& d)const
		{
			return (_year < d._year) ||
				(_year == d._year && _month < d._month) ||
				(_year == d._year && _month == d._month && _day < d._day);
		}

		bool operator>(const Date& d)const
		{
			return (_year > d._year) ||
				(_year == d._year && _month > d._month) ||
				(_year == d._year && _month == d._month && _day > d._day);
		}

		friend ostream& operator<<(ostream& _cout, const Date& d)
		{
			_cout << d._year << "-" << d._month << "-" << d._day;
			return _cout;
		}

	private:
		int _year;
		int _month;
		int _day;
	};

	class PDateLess
	{
	public:
		bool operator()(const Date* p1, const Date* p2)
		{
			return *p1 < *p2;
		}
	};

	class PDateGreater
	{
	public:
		bool operator()(const Date* p1, const Date* p2)
		{
			return *p1 > *p2;
		}
	};

这时候我们创建数据为Date 的优先级队列(大堆),取堆顶元素(判断是否能对自定义类型进行正确调整)

此时没有问题, 因为在实际比较时,调用的是 Date 自己的比较逻辑。

那么如果是Date* 呢?我们再来看:

我们发现多次运行结果不一样 ,因为此时调用的是指针的比较逻辑(地址是随机的,因此结果也是随机的)。

那么,我们要如何去解放呢?可以专门创建一个为Date* 比较的仿函数。

class PDateLess
	{
	public:
		bool operator()(const Date* p1, const Date* p2)
		{
			return *p1 < *p2;
		}
	};

	class PDateGreater
	{
	public:
		bool operator()(const Date* p1, const Date* p2)
		{
			return *p1 > *p2;
		}
	};

这样便没有问题了:

标签:优先级,parent,STL,C++,return,child,const,size,con
From: https://blog.csdn.net/dgfghchhgc/article/details/137054456

相关文章

  • C++ STL- list 的使用以及练习
    目录0.引言1.list介绍 2.list使用2.1构造函数2.2listiterator的使用 3listcapacity 4.listelementaccess 5.listmodifiers 6.list迭代器失效 7.list与vector对vector8.OJ题讲解 删除链表的倒数第N 个节点:0.引言本篇博客我们......
  • 链式栈回文字符串的判断(C++版)
    大家好我是大一新生,如果代码有啥错误和改进的地方可以评论哦,谢谢观念看;#include<iostream>#include<iomanip>usingnamespacestd;#defineok1#defineerror0#defineSelemtypechar#defineStatusint#defineMAXSIZE100typedefstructstack{//链式栈的结构  ......
  • 2023第14届蓝桥杯大赛软件赛省赛C/C++大学A组第6题题解
    目录问题描述:方法一:dfs暴力模拟(45%)方法二:dfs剪枝(100%)问题描述:        小蓝正在一个瓜摊上买瓜。瓜摊上共有n个瓜,每个瓜的重量为Ai。小蓝刀功了得,他可以把任何瓜劈成完全等重的两份,不过每个瓜只能劈一刀。小蓝希望买到的瓜的重量的和恰好为m。请问小蓝至......
  • 使用Docker搭建测试用例管理平台TestLink:简易指南
    简介Testlink是一款免费开源的测试管理软件,基于WEB的测试用例管理系统,主要功能是:测试项目管理、产品需求管理、测试用例管理、测试计划管理、测试用例的创建、管理和执行,并且还提供了统计功能。为了方便快速部署TestLink,并且保持环境的一致性,我们可以使用Docker进行搭建。本文将......
  • whistle.vase的基本用法
    安装w2installwhistle.vase使用启动whistle,进入127.0.0.1:8899/#plugins点击vase,进入vase页面创建对应模版引擎的文件不同的模版引擎用法参考:https://github.com/whistle-plugins/whistle.vase#whistlevase这里的模版引擎中除了whistle.vase内置的script引擎外,其他的......
  • 【华为OD】2024年机试C卷真题集:最新的真题集题库 C/C++/Java/python/JavaScript
    【华为OD】2024年C卷真题集:最新的真题集题库C/C++/Java/python/JavaScript-CSDN博客2024年C卷真题题集题库,有2种分数的题目列表,分别是100分的列表、200分的列表需要订阅请看链接:C卷100分真题集质量分:94价格:39.9元C卷200分真题集质量分:94价格:99.9元从2023年11月开始,华为OD......
  • C++高频面试知识总结 part1
    面向对象1.什么是类?2.面向对象程序设计思想?3.多态的实现?4.动态多态的作用?5.动态绑定的实现?6.纯虚函数的作用以及实现?7.虚函数表如何维护?推荐阅读8.C++struct和类的区别?9.C++中类成员的访问权限?1.什么是类?是一种用户定义的数据类型,包含了数据成员和函数成员。数据成......
  • 【C++从0到1-黑马程序员】STL容器(一)
    ​​​​​​C++从0到1-黑马程序员课程学习笔记课程链接:23string容器-构造函数_哔哩哔哩_bilibili1.String容器1.1string基本概念本质:string是C++风格的字符串,而string本质上是一个类string和char*的区别:char*是一个指针string是一个类,类内部封装了char*,管理这......
  • 【OpenCV】OpenCV (C++) 与 OpenCvSharp (C#) 之间数据通信
     OpenCV是一个基于Apache2.0许可(开源)发行的跨平台计算机视觉和机器学习软件库,可以运行在Linux、Windows、Android和MacOS操作系统上。它轻量级而且高效——由一系列C函数和少量C++类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多......
  • C++复制构造函数、=运算符重载
    C++复制构造函数、=运算符重载#include<iostream>usingnamespacestd;classbase{private:intx,y;public:base():x(2),y(4){cout<<"basedefaultconstructor"<<endl;}base(intx,inty):x(x),y(y){cout<<"base......