服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C++ 超详细讲解stack与queue的使用

C++ 超详细讲解stack与queue的使用

2022-10-28 13:27ymz123_ C/C++

C++ Stack(堆栈) 是一个容器类的改编,为程序员提供了堆栈的全部功能,也就是说实现了一个先进后出(FILO)的数据结构,许多程序都使用了 queue 容器。queue 容器可以用来表示超市的结账队列或服务器上等待执行的数据库事务队

stack

介绍和使用

stack文档介绍

stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。

stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。

stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:

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

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

模拟实现

从栈的接口可以看出,栈实际是一种特殊的vector,直接使用vector即可模拟实现stack

#pragma once
#include <vector>
#include <list>
#include <deque>
#include <iostream>
using namespace std;

namespace s {
	//stack是一个Container适配(封装转换)出来的
	template<class T,class Container = std::deque<T>>
	class stack {
	public:
		stack() {

		}

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

		void push(const T& x)
		{
			_con.push_back(x);
		}

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

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

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

	private:
		Container _con;
	};

	void test_stack1()
	{
		s::stack<int,vector<int>> st;
		//s::stack<int, list<int>> st;
		//s::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;
	}
}

stack的使用例题

最小栈

题目描述:设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。push(x) —— 将元素 x 推入栈中。pop() —— 删除栈顶的元素。top() —— 获取栈顶元素。getMin() —— 检索栈中的最小元素。

解题思路:定义两个成员变量(栈),当插入数据时,_st正常插入,如果要插入的数据比_st的栈顶元素小时,就把该数据也插入_minst。出数据时,取出_st栈顶元素,如果该数据和_minst栈顶数据相等,就把_minst的栈顶元素也取出,这样_minst的栈顶元素就始终是栈中存在元素的最小值。

class MinStack {
public:
  MinStack() {

  }
  
  void push(int val) {
      _st.push(val);
      if(_minst.empty() || val<=_minst.top())
      {
          _minst.push(val);
      }
  }
  
  void pop() {
      if(_st.top()==_minst.top())
      {
          _minst.pop();
      }
      _st.pop();
  }
  
  int top() {
      return _st.top();
  }
  
  int getMin() {
      return _minst.top();
  }

  stack<int> _st;
  stack<int> _minst;
};

栈的弹出压入序列

题目描述:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。

解题思路:定义一个栈,把pushV中的数据依次放入栈中。如果遇到放入的pushV中的数据和popV中数据相等,那么把st栈顶元素取出。最后st如果为空,则符合要求。

class Solution {
public:
  bool IsPopOrder(vector<int> pushV,vector<int> popV) {
      if(pushV.size()==0)
          return true;
      stack<int> st;
      int pushi=0,popi=0;
      while(pushi<pushV.size()){
          st.push(pushV[pushi]);
          //如果pushV[pushi]和popV[popi]匹配上了
          while(!st.empty() && st.top()==popV[popi]){
              st.pop();
              ++popi;
          }
          ++pushi;
      }
      if(st.empty())
          return true;
      return false;
  }
};

逆波兰表达式求值

题目描述:根据 逆波兰表示法,求表达式的值。有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。说明:整数除法只保留整数部分。给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

解题思路:遍历,如果是字符串中是数字,将字符数字转化为数字后放入栈中,如果遇到运算符,取出两个栈顶元素,先取出的栈顶元素作为运算符右边的数字,后取出的作为运算符左边的数字,按照字符串中的运算符做运算,得到的数字再放入栈中,再遍历数组放入数字,以此类推。

class Solution {
public:
  int evalRPN(vector<string>& tokens) {
      stack<int> st;
      int left=0,right=0;
      for(const auto& str:tokens)
      {
          if(str=="+" ||str=="-"||str=="*"||str=="/")
          {
              right=st.top();
              st.pop();
              left=st.top();
              st.pop();
              switch(str[0])
              {
              case '+':
                  st.push(left+right);
                  break;
              case '-':
                  st.push(left-right);
                  break;
              case '*':
                  st.push(left*right);
                  break;
              case '/':
                  st.push(left/right);
                  break;
              }
          }
          else
          {
              st.push(stoi(str));
          }
      }
      return st.top();
  }
};

 

queue

queue的文档介绍

和栈一样,队列是一种容器适配器。该底层容器至少支持以下操作:

empty:检测队列是否为空

size:返回队列中有效元素的个数

front:返回队头元素的引用

back:返回队尾元素的引用

push_back:在队列尾部入队列

pop_front:在队列头部出队列

标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。vector类的头删效率太低,不能头删,所以不适配。

模拟实现

因为queue的接口中存在头删和尾插,因此使用vector来封装效率太低,故可以借助list来模拟实现queue

#pragma once
#include <vector>
#include <list>
#include <deque>
#include <iostream>
using namespace std;

namespace q {

	template<class T,class Container=std::deque<T>>
	class queue {
	public:
		queue() {

		}

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

		void push(const T& x)
		{
			_con.push_back(x);
		}

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

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

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

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

	void test_queue1()
	{
		//q::queue<int,list<int>> q1;
		//q::queue<int, vector<int>> q1;//vector头删效率低,不适配,报错

		q::queue<int> q1;
		q1.push(1);
		q1.push(2);
		q1.push(3);
		q1.push(4);

		while (!q1.empty())
		{
			cout << q1.front();
			q1.pop();
		}
		cout << endl;

	}
}

 

容器适配器

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque

deque简介

deque(双端队列):是一种双开口的"连续"空间的数据结构,即可以在头尾两端进行插入删除操作,且时间复杂度为O(1)。

C++ 超详细讲解stack与queue的使用

deque不是真正完全连续的空间,而是由一段段连续的小空间拼接而成,类似于一个动态的二维数组。

C++ 超详细讲解stack与queue的使用

deque底层是一段假想的连续空间,实际上是分段连续的,它借助迭代器来维护其假想连续的结构,因此deque的迭代器设计也比较复杂。

C++ 超详细讲解stack与queue的使用

vector的缺点是当空间不够时要扩容,会存在一定的空间浪费,头插或中间插入需要挪动数据,效率低。优点是可以随机访问,cpu高速缓存命中率很高。list的缺点是无法随机访问,cpu高速缓存命中率低(地址不连续)。优点是可按需申请释放空间,任意位置的插入删除数据时间复杂度为O(1),效率高。而deque折中了vector和list,从使用的角度,避开了它们的缺点,可以支持随机访问,头尾插入效率也较高,扩容时不需要挪动大量数据,效率较vector高。但deque不够极致,随机访问效率不如vector,任意位置插入删除不如list,且中间插入删除数据也很麻烦,效率不高,需要挪动整体数据,或是挪动目标buff数组,并记录每个buff数组的大小。在遍历时,deque的迭代器需要频繁检测是否移动到某段小空间的边界,效率低下。所以deque比较适合头插尾插多的情况,比如作为stack和queue的底层数据结构。stack和queue不需要遍历(所以没有迭代器),只需要在固定的两端进行操作。stack中元素增长时,如果需要扩容,deque的效率比vector高;queue同理,且内存使用率高。

 

priority_queue优先级队列

priority_queue文档介绍

优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。

类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。

优先队列被实现为容器适配器,即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的尾部弹出,其成为优先队列的顶部。

底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:

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

标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。

需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。

priority_queue的使用

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了对算法将vector中元素构造成堆的结构,因此priority_queue就是堆。注意:默认情况下priority_queue是大堆。

在OJ中的使用:

数组中的第k个最大元素

题目描述:给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

class Solution {
public:
  int findKthLargest(vector<int>& nums, int k) {
      priority_queue<int> pq(nums.begin(),nums.end());
      //默认大堆,取出前k-1个元素后的第k个堆顶元素就是第k大的元素
      while(--k)
      {
          pq.pop();
      }
      
      return pq.top();
  }
};

priority_queue的模拟实现

#pragma once
#include <vector>
#include <list>
#include <iostream>
using namespace std;

namespace priority
{
	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;
		}
	};

	//模板参数--类型
	//函数参数--对象

	//less 大堆
	template<class T,class Container=::vector<T>,class Compare=Less<typename Container::value_type>>
	class priority_queue
	{
	public:
		priority_queue()
		{}

		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{
			//插入数据
			while (first != last)
			{
				_con.push_back(*first);
				++first;
			}
			//建堆
			for (int i = (_con.size() - 1 - 1) / 2;i >= 0;i--)
			{
				AdjustDown(i);
			}
		}

		void AdjustDown(size_t parent)
		{
			Compare com;
			int child = parent * 2+1;
			while (child <_con.size())
			{
				if (child + 1 < _con.size() && com(_con[child] , _con[child + 1]))
				{
					child++;
				}
				//_con[parent] < _con[child]
				if (com(_con[parent] , _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

		void AdjustUp(size_t child)
		{
			Compare com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (com(_con[parent] , _con[child]))
				{
					swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				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()
		{
			return _con.size();
		}

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

	private:
		Container _con;
	};

	void test_priority_queue()
	{
		list<int> lt = { 1,2,3,4,5, };
		priority_queue<int, vector<int>, Greater<int>> pq(lt.begin(), lt.end());

		pq.push(10);
		pq.push(20);
		pq.push(30);
		pq.push(40);
		pq.push(50);
		pq.push(60);

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

C++ 超详细讲解stack与queue的使用

通过仿函数控制比较方式

如果在priority_queue中放自定义类型的数据,我们需要在自定义类型中重载>或<。如以下情形,类型T是Date*,如果不重载<或>,比较的就是地址大小。

//仿函数的变异玩法--通过仿函数控制比较方式
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);
	}

	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend class PDateLess;

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

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

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

int main()
{
	priority_queue<Date*, vector<Date*>, PDateLess> pq;
	pq.push(new Date(2023, 11, 24));
	pq.push(new Date(2021, 10, 24));
	pq.push(new Date(2023, 11, 14));
	pq.push(new Date(2022, 1, 24));

	cout << (*pq.top()) << endl;
	pq.pop();

	return 0;

}

C++ 超详细讲解stack与queue的使用

到此这篇关于C++ stack和queue的文章就介绍到这了,更多相关C++ stack内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/mmz123_/article/details/121505391

延伸 · 阅读

精彩推荐
  • C/C++C语言手把手教你实现贪吃蛇AI(下)

    C语言手把手教你实现贪吃蛇AI(下)

    这篇文章主要手把手教你实现C语言版贪吃蛇AI,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    kuweicai11922021-06-19
  • C/C++利用C语言实现三子棋游戏

    利用C语言实现三子棋游戏

    这篇文章主要为大家详细介绍了利用C语言实现三子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    yuelinghou4102021-10-22
  • C/C++FFRPC应用 Client/Server使用及原理解析

    FFRPC应用 Client/Server使用及原理解析

    这篇文章主要介绍了FFRPC应用 Client/Server使用及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可...

    知然6462021-08-01
  • C/C++详谈C++何时需要定义赋值/复制构造函数

    详谈C++何时需要定义赋值/复制构造函数

    下面小编就为大家带来一篇详谈C++何时需要定义赋值/复制构造函数。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    C++教程网11792021-04-26
  • C/C++c++实现的常见缓存算法和LRU

    c++实现的常见缓存算法和LRU

    LRU缓存算法也叫LRU页面置换算法,是一种经典常用的页面置换算法,下面这篇文章主要介绍了c++实现的常见缓存算法和LRU,需要的朋友可以参考借鉴,下面来...

    cpselvis5352021-04-27
  • C/C++C 读取ini文件的实例详解

    C 读取ini文件的实例详解

    这篇文章主要介绍了C 读取ini文件的实例详解的相关资料,希望通过本文能帮助到大家,让大家实现这样的功能,需要的朋友可以参考下...

    dai_jing9132021-06-08
  • C/C++C语言中sizeof函数的基本使用总结

    C语言中sizeof函数的基本使用总结

    这篇文章主要给大家介绍了关于C语言中sizeof函数的基本使用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价...

    花火·12292021-06-22
  • C/C++socket编程的详细讲解

    socket编程的详细讲解

    本文详细讲解了socket编程,它是网络中经常使用的一门技术,该文章通过大量的代码来解释,大家可以参考参考...

    枫飞飞11882021-12-18