C++ queue priority_queuestack 详解及模拟实现

news2025/1/19 20:25:06

1. stack的介绍和使用

1.1 stack的介绍

1. stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
2. stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
3. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下
操作:

  • empty:判空操作
  • back:获取尾部元素操作
  • push_back:尾部插入元素操作
  • pop_back:尾部删除元素操作

4. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。

5.不支持迭代器:要保证先进先出。


1.2 stack的使用

函数说明接口说明
stack()构造空的栈
empty()检测stack是否为空
size()返回stack中元素的个数
top()返回栈顶元素的引用
push()将元素val压入stack中
pop()将stack中尾部的元素弹出

代码示例:

void test_stack1()
{
	stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);

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



2.模拟实现stack  

        从栈的接口中可以看出,栈实际是一种特殊的vector,因此使用vector完全可以模拟实现stack。
    适配器模式--转换(在这里我们使用vector就行转换
    为了代码的灵活性(fanx,利用模板接收不同的容器
    函数参数传递的是对象,而模板参数传递的是类型,模板参数也是可以缺省的

namespace mystack {

	template<class T, class Container = vector<T>>
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

		void pop()
		{
			_con.pop_back();
		}

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

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

		const T& top()
		{
			return _con.back();
		}
	private:
		Container _con;
	};
}

测试:

	void test()
	{
		stack<int> st;
		st.push(1);
		st.push(2);
		st.push(3);
		st.push(4);

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

测试结果:


3. queue的介绍和使用

3.1queue的介绍
1. 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:

  • empty:检测队列是否为空
  • size:返回队列中有效元素的个数
  • front:返回队头元素的引用
  • back:返回队尾元素的引用
  • push_back:在队列尾部入队列
  • pop_front:在队列头部出队列

4. 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。

3.2queue的使用

函数声明接口说明
queue()构造空的队列
empty()检测队列是否为空,是返回true,否则返回false
size()返回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在队尾将元素val入队列
pop()将队头元素出队列

示例:

void test_queue()
{
	queue<int> qe;
	qe.push(1);
	qe.push(2);
	qe.push(3);
	qe.push(4);
	while (!qe.empty())
	{
		cout << qe.front() << " ";
		qe.pop();
	}
	cout << endl;
}


4.queue模拟实现前言 

因为queue的接口中存在头删和尾插,因此使用vector来封装效率太低,因为我们直接使用库中queue默认的容器deque(双端队列)来实现。


5.deque的简单介绍

deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:
那deque是如何借助其迭代器维护其假想连续的结构呢


5.1deque的缺陷 

与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。

与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。
 


5.2 为什么选择deque作为stack和queue的底层默认容器

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和
queue默认选择deque作为其底层容器,主要是因为:

  • 1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
  • 2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。

结合了deque的优点,而完美的避开了其缺陷。

 


6.模拟实现queue 

这里我们就使用deque作为底层默认容器来实现queue了

namespace bit
{
	template<class T, class Container = deque<T>>
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

		void pop()
		{
			_con.pop_front();
		}

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

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

		const T& front()
		{
			return _con.front();
		}

		const T& back()
		{
			return _con.back();
		}
	private:
		Container _con;
	};

测试:

	void testmy_queue()
	{
		queue<int> qe;
		qe.push(1);
		qe.push(2);
		qe.push(3);
		qe.push(4);
		while (!qe.empty())
		{
			cout << qe.front() << " ";
			qe.pop();
		}
		cout << endl;
	}

测试结果:

当然你也可以使用list作为底层容器:

	void testmy_queue()
	{
		queue<int,list<int>> qe;
		qe.push(1);
		qe.push(2);
		qe.push(3);
		qe.push(4);
		while (!qe.empty())
		{
			cout << qe.front() << " ";
			qe.pop();
		}
		cout << endl;
	}


7. priority_queue


7.1仿函数 

在学习priority queue之前我们先来了解一下仿函数

什么是仿函数(函数对象)?

        仿函数就是假函数,它是把对象当作函数使用,所以也称为函数对象。因为普通函数在某些特殊场景下使用比较麻烦,所以就诞生了仿函数。

        如何实现仿函数?

        重载()运算符即可。

 实现仿函数
        代码中定义Less类,重载(),函数中定义了a、b两个参数,当a小于b就返回true,否则返回false。

        在main函数中创建了Less类的对象,如果想要调用重载(),常规的调用方法应该是对象名.函数名(参数列表)。但因为重载()函数是可以省略.operator()的,所以我们可以使用简化的方式调用,这样是不是看起来跟使用普通函数一模一样了。这就是仿函数的使用。

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

int main()
{
	
	Less<int> lessfunc;
	cout << lessfunc(1, 2) << endl;
	return 0;
}


7.2priority_queue的使用和介绍 

7.2.1 priority_queue的介绍
1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。
3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:

  • empty():检测容器是否为空
  • size():返回容器中有效元素个数
  • front():返回容器中第一个元素的引用
  • push_back():在容器尾部插入元素
  • pop_back():删除容器尾部元素

5. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。

7.优先级队列的参数很重要,因为这里有很多东西要用到,比如仿函数。当然这里只看一些常用的。

priority_queue类模板参数
        这是priority_queue类的模板参数列表,里面有三个参数:

class T,class Container = vector<T>,class Compare = less<typename 
Container::value_type> 

在实现建立大堆或者小队的时候就存在着比较逻辑,那么如何来控制呢,这里就涉及到了仿函数的知识了,在模拟实现的时候再做说明。

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

函数声明接口说明
priority_queue()/priority_queue(first,
last)
构造一个空的优先级队列
empty( )检测优先级队列是否为空,是返回true,否则返回
false
top( )返回优先级队列中最大(最小元素),即堆顶元素
push(x)在优先级队列中插入元素x
pop()删除优先级队列中最大(最小)元素,即堆顶元素

示例:

1.建大堆

void test1()
{
	priority_queue<int>a;//默认是大堆
	//大堆的完整写法
	//priority_queue<int,vector<int>,less<int>>a;
	int n = 0;
	cin >> n;
	int x;
	for (int i = 0; i < n; i++) {
		cin >> x;
		a.push(x);
	}
	while (!a.empty()) {
		cout << a.top() << " ";
		a.pop();
	}
}

2.建立小堆

        因为priority_queue是模板,所以创建对象时需要传入模板参数,但是由于模板参数内部是具有默认值的,所以创建大堆时可以只传递元素类型即可。但创建小堆的时候,模板参数是不可以省略的。

void test2()
{
	priority_queue<int, vector<int>,greater<int>>a;

	int n = 0;
	cin >> n;
	int x;
	for (int i = 0; i < n; i++) {
		cin >> x;
		a.push(x);
	}
	while (!a.empty()) {
		cout << a.top() << " ";
		a.pop();
	}
}


 8.模拟实现priority_queue

优先级队列就是用来创建大堆和小堆的,实现优先级队列实际上就是实现堆。一般情况下:

我们建堆使用向上调整算法,删除根节点的时候我们使用向下调整算法。

(关于堆详情请看这篇文章:如果对堆不了解一定要看一下数据结构与算法--特殊的完全二叉树--堆,堆排序,利用堆解决topk的问题-CSDN博客)

通过改变这两种算法的比较逻辑,我们就可以控制是建大堆还是建小堆了。这里我们就需要用到仿函数了,仿函数在这里的作用就是控制比较逻辑,仿函数作为类就可以通过模板参数进行传递,进而控制类中需要进行比较的部分了。

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

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

	template<class T, class Container = vector<T>, class Compare = less<T>>
	class priority_queue
	{
	public:
		void adjust_up(size_t child)
		{
			Compare com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[child] > _con[parent])
				//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 push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}

		void adjust_down(size_t parent)
		{
			Compare com;
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				//if (child + 1 < _con.size() && _con[child + 1] > _con[child])
				//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					++child;
				}

				//if (_con[child] > _con[parent])
				//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 pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjust_down(0);
		}

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

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

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

	private:
		Container _con;
	};
};

这里以向上调整算法为例:

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

        conpare作用一个类型,com作为它的对象,这个对象可以像函数一样去使用,完成比较逻辑。传less在这里的意思就是,如过父亲小于孩子,那么就执行交换孩子和父亲的操作,这就是建立大堆的比较逻辑,反之greater就是建立小堆的逻辑了。

测试:建一个小堆

	void test()
	{
		priority_queue<int, vector<int>,greater<int>>a;

		int n = 0;
		cin >> n;
		int x;
		for (int i = 0; i < n; i++) {
			cin >> x;
			a.push(x);
		}
		while (!a.empty()) {
			cout << a.top() << " ";
			a.pop();
		}
	}

测试结果:正确!

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

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

相关文章

【华为笔试题汇总】2024-04-17-华为春招笔试题-三语言题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是KK爱Coding &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为近期的春秋招笔试题汇总&#xff5e; &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f…

吴恩达2022机器学习专项课程(一) 第二周课程实验:特征工程和多项式回归(Lab_04)

目标 探索特征工程和多项式回归&#xff0c;使用线性回归来拟合非常复杂甚至非线性的函数。 1.为什么线性回归能拟合非线性函数&#xff1f; fxw*xb&#xff0c;属于线性回归的扩展&#xff0c;这个公式在数学中不属于线性&#xff0c;因为有x&#xff0c;而在机器学习中属于…

【云计算】云数据中心网络(六):私网连接

云数据中心网络&#xff08;六&#xff09;&#xff1a;私网连接 1.什么是私网连接2.私网连接的组成3.私网连接的优势4.私网连接的主要应用场景 前面讲到 VPC 网络具有隔离性&#xff0c;VPC 之间无法通信。当一个 VPC 中的终端需要访问部署在另一个 VPC 中的服务时&#xff0c…

江苏开放大学2024年春《测试技术 060245》作业3参考答案

答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 单选题 1相关系数ρxy的取值范围处于( )之间。 A-1和0 B…

OpenCV基本图像处理操作(十一)——图像特征Sift算法

图像尺度空间 在一定的范围内&#xff0c;无论物体是大还是小&#xff0c;人眼都可以分辨出来&#xff0c;然而计算机要有相同的能力却很难&#xff0c;所以要让机器能够对物体在不同尺度下有一个统一的认知&#xff0c;就需要考虑图像在不同的尺度下都存在的特点。 尺度空间的…

WebGl/Three 粒子系统 人物破碎及还原运动

粒子 首先&#xff0c;加载模型&#xff0c;这是万千粒子的前身&#xff0c;模型对象由很多面构成&#xff0c;这些面又是由各个点构成的&#xff0c;所以可以将模型的几何体对象geometry赋给粒子对象&#xff0c;粒子物体用Points方式渲染 bloader.load("obj/female02/Fe…

斯坦福发布法律指令数据集LawInstruct,统一17个辖区24种语言

在法律领域&#xff0c;语言模型&#xff08;Language Models, LLMs&#xff09;的发展一直面临着独特的挑战。法律文本的复杂性、专业术语的广泛使用以及对精确性和可靠性的极高要求&#xff0c;使得法律领域的自然语言处理&#xff08;Natural Language Processing, NLP&…

新版周易运势风水测算 宝宝起名改名 公司吉凶测名 八字姻缘爱情算命预测 易经塔罗牌占卜

源码简介&#xff1a; 系统包含八字合婚、流年运势、宝宝起名、塔罗占卜、姓名配对、命中注定、星座运势、八字精批、桃花运姻缘、测终生运、十年大运详批、犯太岁化解、紫薇财运精批、取名改名、算姻缘、婚前合婚、算事业、算财运、姓名详批、塔罗爱情占卜&#xff08;三个月…

Python接口自动化 —— Web接口(2)

1.2.3 HTTP HTTP概念 Hyper Text Transfer Protocal超文本传输协议&#xff0c;基于tcp/ip通信协议来传递数据&#xff0c;属于应用层协议主要特点: 无连接: 每次连接只处理一个请求&#xff0c;服务器处理完请求并受到客户端应答后就断开连接媒体独立。 只要服务器和客…

web项目中jsp页面不识别el表达式

如果使用el表达式出现下图问题 ** 解决办法 ** 这是因为maven创建项目时&#xff0c;web.xml头部声明默认是2.3&#xff0c;这个默认jsp关闭el表达式 修改web.xml文件开头的web-app的版本 <?xml version"1.0" encoding"UTF-8"?> <web-app x…

gpt4.0人工智能网页版

在最新的AI基准测试中&#xff0c;OpenAI几天前刚刚发布的GPT-4-Turbo-2024-04-09版本&#xff0c;大幅超越了Claude3 Opus&#xff0c;重新夺回了全球第一的AI王座。 GPT-4-Turbo-2024-04-09版本是目前国内外最强的大模型&#xff0c;官网需要20美元每月才能使用&#xff0c;…

RUM 最佳实践-视觉稳定性的探索与实践

写在前面的话 在当今数字时代&#xff0c;网页的视觉稳定性对于提供良好的用户体验至关重要。其中一个衡量视觉稳定性的关键指标就是累积布局偏移&#xff08;Cumulative Layout Shift&#xff0c;简称 CLS&#xff09;。CLS 作为 Web Vitals 指标之一&#xff0c;它衡量的是网…

全球半导体排行:台积电登顶,中芯国际第24位

最新发布的《麦克莱恩报告》4月更新揭示了2023年全球前50大半导体供应商的最终排名情况&#xff0c;其中前25强名单尤为引人注目。台积电&#xff08;TSMC&#xff09;凭借强劲的市场表现和业务增长&#xff0c;成功超越其他竞争对手&#xff0c;跃居全球半导体供应商首位。与此…

资料总结分享:《全外显子测序数据的流程和原理》

1外显子与测序&#xff0c;生信流程 1.1 外显子是什么&#xff1f; 外显子是基因组中能够转录组出成熟RNA的部分。一个基因组中所有外显子的集合&#xff0c;即为外显子组。值得注意的是&#xff0c;通常所说的全外显子组测序&#xff0c;是指针对蛋白编码基因的外显子&#x…

独立开源版:零点城市社交电商v2.1.2.4

源码介绍 独立版&#xff1a;零点城市社交电商v2.1.2.4 新增首页新增多弹窗 注意&#xff1a;如果没有此完整程序勿下载 全开源解密版代码&#xff0c;后端完全开源&#xff0c;前端是VUE前端&#xff0c;可自行二开自己想要的功能。 独立版零点城市社交电商 小程序致力于…

gpt4和chatgpt的区别

模型规模和性能&#xff1a;GPT-4比GPT-3.5更大、更强大。GPT-4拥有更多的参数和更大的训练数据集&#xff0c;因此在各种任务上表现更出色&#xff0c;如语言理解、问题解答和推理能力等。多模态能力&#xff1a;GPT-4支持处理图像等多模态信息&#xff0c;而GPT-3.5主要处理文…

原生实现ajax

1 什么是ajax AJAX Asynchronous JavaScript and XML&#xff08;异步的 JavaScript 和 XML&#xff09;。 AJAX 不是新的编程语言&#xff0c;而是一种使用现有标准的新方法。 AJAX 最大的优点是在不重新加载整个页面的情况下&#xff0c;可以与服务器交换数据并更新部分网…

雨伞-浅色脚本

渲染参考&#xff1a;明亮/干净/高级 静帧参考 解说 镜头时长 效果参考 中景画面展示3把竖着的浅色的伞 1s / 特写展示伞把手 1s 中景展示雨伞全貌 2s 微观镜头 缝线动画 3s 镜头旋转至伞面微观材质镜头&#xff0c;展现其多层结构 10s 微观镜头 水珠滑动在伞…

18.读取指定目录下的txt文档时,调用另外一个python文件

1.题目 遍历4K_phone和4K_VR目录下的所有txt文件&#xff0c;并将它们的内容合并到一个名为4k_decoding.txt的文件中。 但是&#xff0c;假设你有一个名为another_script.py的Python文件&#xff0c;你想在合并txt文件之前执行它生成要处理的txt文档。 最后统计完原始的txt文件…

python使用uiautomator2操作真机(华为Honor 10)

环境&#xff1a; python3.8.10&#xff0c;华为手机Honor 10(6G,64g)&#xff0c;版本android 9。 之前写过一篇文章&#xff1a; python使用uiautomator2操作真机_python uiautomator2 控制真机-CSDN博客 今天再拿另外一部手机测试。 一、将手机设置为开发者模式 1、设…