【STL源码剖析】priority_queue 优先队列的简单实现

news2024/11/18 23:41:34

水到绝处是风景

人到绝境是重生


目录

priority_queue的模拟实现 

源码剖析:

代码测试:

 契子✨ 


我们之前不仅讲过 队列queue 还有 双端队列deque 而我们今天所讲的依旧是队列家族的成员 -- 优先队列priority_queue

顾名思义,priority_queue是一个拥有权值观念的 queue,它允许增删元素、访问元素等功能。由于这是一个 queue,所以只允许在低端加入元素,并从顶端取出元素,除此之外别无其他存取元素的途径

priority_queue 带有权值观念,其内的元素并非依照推入的顺序排序,而是自动依照元素的权值排序(权值通常以实值表示)。权值最高者,排在前面

大家想象一下,我们之前学过的数据结构有哪一种具有类似的性质?

是不是像我们学过的 -- 堆(heap),我们可以利用 heap 的特性完成 [依权值高低自动递增排序] priority_queue

优先队列 priority_queue 是一种容器适配器,默认使用 vector 作为其底层存储数据的容器,在 vector 上又使用了堆算法将 vector 中元素构造成堆的结构,因此 priority_queue 就是堆,所有需要用到堆的位置,都可以考虑使用 priority_queue。注意: 默认情况下 priority_queue 是 大堆

 priority_queue 没有迭代器

priority_queue 的所有元素,进出都有一定的规则,只有 queue 的顶端元素(权值最高元素),才有机会被外界取用,priority_queue 不提供遍历功能,也不提供迭代器功能


priority_queue的模拟实现 

通过对 priority_queue 的底层结构默认就是 vector ,然后我们处理一下形成堆,因此此处只需对对进行通用的封装即可。操作非常简单,源码很简短,这里就完整的列出吧 ~ 然后在讲一下细节

#include<vector>
#include<iostream>
using std::vector;
using std::swap;

namespace Mack
{
	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 Sequence = vector<T>, class Comapre = less<T> >
	class priority_queue
	{
	public:
		
		priority_queue() = default;

		template <class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				c.push_back(*first);
				++first;
			}
			for (int i = (c.size() - 1 - 1) / 2; i >= 0; i--)
			{
				AdjustDown(i);
			}
		}

		const T& top() const
		{
			return c.front();
		}

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

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

		void AdjustUP(size_t child)
		{
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				if (comp(c[parent] , c[child]))
				{
					swap(c[parent], c[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		void push(const T& val)
		{
			c.push_back(val);
			AdjustUP(size()-1);
		}

		void AdjustDown(size_t parent)
		{
			size_t child = parent * 2 + 1;
			while (child < size())
			{
				while (child + 1 < size() && comp(c[child] , c[child+1]))
				{
					child++;
				}
				if (comp(c[parent] , c[child]))
				{
					swap(c[parent], c[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

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

	private:
		Sequence c;
		Comapre comp;
	};

}

我们先来分析一下库里面的优先队列

对啦 ~ 头文件依然是 #include<queue>

源码剖析:

#include<queue>
#include<iostream>
using namespace std;
void priority_queue_test()
{
	priority_queue<int>  str;
	str.push(10);
	str.push(30);
	str.push(20);
	str.push(50);
	str.push(35);
	while (!str.empty())
	{
		cout << str.top() << " ";
		str.pop();
	}
}

我们发现库里的优先队列默认排的是降序也就是大堆 ~ 

所以我们写优先队列时,也要按照大堆的方式去写

关于算法,老铁们可以借鉴一下这个:二叉堆

我们重点讲一下关于 priority_queue 的自动排序,我们知道我们现在的优先队列排的是降序,那我们想排升序怎么办呢?难道要将堆中的比较符号都改一下吗?

我们先来看一下库里的算法:

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

void priority_queue_test()
{
	priority_queue<int, vector<int>,greater<int>>  str;
	str.push(10);
	str.push(30);
	str.push(20);
	str.push(50);
	str.push(35);
	while (!str.empty())
	{
		cout << str.top() << " ";
		str.pop();
	}
}

用惯排序 sort 的老铁可能会有些不习惯,为什么中间还要加一个参数,因为库里就是以下的格式,就跟传缺省一样不能隔代相传

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

我们回到重点!!!

在我们我们 C语言 阶段的话频繁的比较大小我们一般都会写成一个函数 

bool Compare(int x, int y)
{
	return x < y;
}

int main()
{
	int x = 0, y = 1;
	if (Compare(x, y))
	{
		printf("y>x");
	}
	else
	{
		printf("y<x");
	}
	return 0;
}

如果比较 int 我们写一个专门比较 int 类型的函数,char 类型则专门写一个char 类型的函数

当我们学了 C++ 就开摆了,编程的进步就是变懒的过程 -- 我们可以利用模板来控制类型的比较

而要使用模板的前提必须是一个类,或者类中的函数

	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;
		}
	};

所以我们用一个类去包含比较函数在利用模板,而我们重载()的原因就是想写成这样一种函数的形式:Compare(x, y) -- 这样方便比较

我们调用类中的函数是不是都是 类对象+点运算符,我们将()重载便可以写成函数的形式

这样的函数形式我们称之为伪函数

为了让我们得初始化方便,库里提供了迭代器区间构造

有些老铁可能会疑惑,不是不提供迭代器吗,怎么还会有迭代器区间构造?
嘿嘿 ~ 其实我们的数组也可以进行迭代

		template <class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				c.push_back(*first);
				++first;
			}
			for (int i = (c.size() - 1 - 1) / 2; i >= 0; i--)
			{
				AdjustDown(i);
			}
		}

先将数据尾插到对象中,在向下调整建堆,因为向下调整要找到第一个非叶子节点  

这里放张动图以便老铁理解:

(_con.size() - 1 - 1) / 2 的 _con.size() - 1 是找到最后一个节点,(_con.size() - 1 - 1) / 2,则是套公式 parent  = (child-1) /2 找到最后一个节点的双亲也就是第一个非叶子节点


代码测试:

别的不说先来测试一下代码,不要哔哔了一大段文字结果代码都是错的

void priority_queue_test()
{
	int arr[] = {1,3,5,7,9,2,4,6,8,0};
	priority_queue<int, vector<int>> str(arr, arr + 9);

	while (!str.empty())
	{
		std::cout << str.top() << " ";
		str.pop();
	}
}

 

#include"priority_queue.h"
#include<iostream>
using std::iostream;
using std::ostream;
using namespace Mack;

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;
};

void TestPriorityQueue()
{
	priority_queue<Date> q1;
	q1.push(Date(2018, 10, 29));
	q1.push(Date(2018, 10, 28));
	q1.push(Date(2018, 10, 30));
	std::cout << q1.top() << std::endl;
	priority_queue<Date, vector<Date>, greater<Date>> q2;
	q2.push(Date(2018, 10, 29));
	q2.push(Date(2018, 10, 28));
	q2.push(Date(2018, 10, 30));
	std::cout << q2.top() << std::endl;
}

int main()
{	
	TestPriorityQueue();
	std::cout << std::endl;
	system("pause");
	return 0;
}


 

 有问题的话可以提出来哦 ~ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1799324.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】进程2——管理概念,进程概念

1.什么是管理&#xff1f; 那在还没有学习进程之前&#xff0c;就问大家&#xff0c;操作系统是怎么管理进行进程管理的呢&#xff1f; 很简单&#xff0c;先把进程描述起来&#xff0c;再把进程组织起来&#xff01; 我们拿大学为例子 最典型的管理者——校长最典型的被管理…

在keil5中打开keil4工程的方法

文章目录 1. 打开文件 2. 安装旧版本包 3. 在keil4中打开keil5工程 1. 打开文件 在keil5 MDK的环境下&#xff0c;打开keil4的工程文件&#xff0c;会弹出下图所示的窗口&#xff1a; 参考官网的解释这两个方法分别为&#xff1a; 1. 使用MDK 版本 4 Legacy Pack时&#x…

Vue08-数据代理

一、Object.defineProperty() Object.defineProperty() 是 JavaScript 中的一个方法&#xff0c;用于直接在一个对象上定义一个新属性&#xff0c;或者修改一个对象的现有属性&#xff0c;并返回这个对象。 这个方法允许你精确地控制一个对象的属性&#xff0c;包括它的值、是…

2048小游戏的菜鸡实现方法

# 2048小游戏的实现与分析 2048是一款非常受欢迎的数字滑块游戏&#xff0c;其目标是通过滑动和合并相同数字的方块来创建一个值为2048的方块。下面&#xff0c;我们将通过分析一个C语言实现的2048小游戏的源代码&#xff0c;来探索如何用编程实现这款游戏。 ## 游戏概述 20…

Day12:rem 布局 和 less 使用

目标&#xff1a;使用 rem 和 less 完成移动端的布局。 一、移动 Web 基础 1、谷歌模拟器 在网页右键点“检查”或快捷键 F12&#xff0c;然后右边栏顶部第二个按钮切换设备为移动端&#xff0c;刷新网页&#xff0c;可以看到谷歌模拟器&#xff0c;可以切换模拟器型号、尺寸…

Vue3_上传文件_下载文件

目录 一、上传文件 二、下载文件 vue3对接后端进行文件上传和下载。 一、上传文件 点击上传资料按钮&#xff0c;选择文件&#xff0c;进行上传。 创建一个proFile.vue&#xff0c;文件&#xff0c;这个文件可以作为一个子组件在其他页面引用。 组件用的element-Plus的ElM…

端午假期来临,来使用闪侠惠递便宜寄快递吧!

相信很多人和我一样&#xff0c;每当需要寄快递时&#xff0c;总是感到十分头疼。不同的快递公司有不同的价格、时效和服务质量等等&#xff0c;选择起来真的很不容易。但是现在有了闪侠惠递来帮大家寄快递吧&#xff0c;这个问题就可以迎刃而解了&#xff01;小编奉劝大家快来…

NSSCTF CRYPTO MISC题解(一)

陇剑杯 2021刷题记录_[陇剑杯 2021]签到-CSDN博客 [陇剑杯 2021]签到 下载附件压缩包&#xff0c;解压后得到 后缀为.pcpang&#xff0c;为流量包&#xff0c;流量分析&#xff0c;使用wireshark打开 {NSSCTF} [陇剑杯 2021]签到 详解-CSDN博客 选择统计里面的协议分级 发现流…

JVMの堆、栈内存存储

1、JVM栈的数据存储 通过前面的学习&#xff0c;我们知道&#xff0c;将源代码编译成字节码文件后&#xff0c;JVM会对其中的字节码指令解释执行&#xff0c;在解释执行的过程中&#xff0c;又利用到了栈区的操作数栈和局部变量表两部分。 而局部变量表又分为一个个的槽位&…

接口的应用、 适配器设计模式

接口的应用 适配器设计模式 Inter package com.itheima.a09;public interface Inter {public abstract void show1();public abstract void show2();public abstract void show3();public abstract void show4();}InterAdapter package com.itheima.a09; //抽象 public abs…

WPF中读取Excel文件的内容

演示效果 实现方案 1.首先导入需要的Dll(这部分可能需要你自己搜一下) Epplus.dll Excel.dll ICSharpCode.SharpZipLib.dll 2.在你的解决方案的的依赖项->添加引用->浏览->选择1中的这几个Dll点击确定。(添加依赖) 3.然后看代码内容 附上源码 using Excel; usi…

苍穹外卖笔记-08-套餐管理-增加,删除,修改,查询和起售停售套餐

套餐管理 1 任务2 新增套餐2.1 需求分析和设计接口设计setmeal和setmeal_dish表设计 2.2 代码开发2.2.1 根据分类id查询菜品DishControllerDishServiceDishServiceImplDishMapperDishMapper.xml 2.2.2 新增套餐接口SetmealControllerSetmealServiceSetmealServiceImplSetmealMa…

阿里通义千问,彻底爆了!(本地部署+实测)

点击“终码一生”&#xff0c;关注&#xff0c;置顶公众号 每日技术干货&#xff0c;第一时间送达&#xff01; 问大家一个问题&#xff1a;你是否想过在自己的电脑上部署一套大模型&#xff1f;并用自己的知识库训练他&#xff1f; 阿里通义千问今天发布了最新的开源大模型系…

【转】ES, 广告索引

思考&#xff1a; 1&#xff09;直接把别名切换到上一个版本索引 --解决问题 2&#xff09;广告层级索引如何解决&#xff1f; -routing、join 3&#xff09;查询的过程&#xff1a;query and fetch, 优化掉fetch 4&#xff09;segment合并策略 5&#xff09;全量写入时副…

二轴机器人大米装箱机:技术创新引领智能包装新潮流

在科技日新月异的今天&#xff0c;自动化和智能化已成为各行各业追求高效、精准生产的关键。作为粮食加工行业的重要一环&#xff0c;大米装箱机的技术创新与应用价值日益凸显。其中&#xff0c;二轴机器人大米装箱机以其高效、稳定、智能的特点&#xff0c;成为市场的新宠。星…

IT学习笔记--Flink

概况&#xff1a; Flink 是 Apache 基金会旗下的一个开源大数据处理框架。目前&#xff0c;Flink 已经成为各大公司大数据实时处理的发力重点&#xff0c;特别是国内以阿里为代表的一众互联网大厂都在全力投入&#xff0c;为 Flink 社区贡献了大量源码。 Apache Flink 是一个…

SQL进阶day10————多表查询

目录 1嵌套子查询 1.1月均完成试卷数不小于3的用户爱作答的类别 1.2月均完成试卷数不小于3的用户爱作答的类别 ​编辑1.3 作答试卷得分大于过80的人的用户等级分布 2合并查询 2.1每个题目和每份试卷被作答的人数和次数 2.2分别满足两个活动的人 3连接查询 3.1满足条件…

嵌入式Linux系统编程 — 2.1 标准I/O库简介

目录 1 标准I/O库简介 1.1 标准I/O库简介 1.2 标准 I/O 和文件 I/O 的区别 2 FILE 指针 3 标准I/O库的主要函数简介 4 标准输入、标准输出和标准错误 4.1 标准输入、标准输出和标准错误概念 4.2 示例程序 5 打开文件fopen() 5.1 fopen()函数简介 5.2 新建文件的权限…

分享:各种原理测厚仪的发展历程!

板材厚度的检测离不开测厚仪的应用&#xff0c;目前激光测厚仪、射线测厚仪、超声波测厚仪等都已被广泛的应用于板材生产线中&#xff0c;那你了解他们各自的发展历程吗&#xff1f; 激光测厚仪的发展&#xff1a; 激光测厚仪是随着激光技术和CCD&#xff08;电荷耦合器件&…

如何挑选最适合你的渲染工具

随着技术的发展&#xff0c;云渲染平台逐渐成为设计师、动画师、影视制作人员等创意工作者的得力助手。然而&#xff0c;市场上的云渲染平台种类繁多&#xff0c;如何选择最适合自己的渲染工具成为了一个需要认真考虑的问题。 在挑选适合自己的云渲染工具时&#xff0c;我们需…