返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++ 超详细讲解stack与queue的使用
  • 618
分享到

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

2024-04-02 19:04:59 618人浏览 八月长安
摘要

目录stack介绍和使用模拟实现stack的使用例题最小栈栈的弹出压入序列逆波兰表达式求值queue模拟实现容器适配器deque简介priority_queue优先级队列priori

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)。

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

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

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

通过仿函数控制比较方式

如果在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内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++ 超详细讲解stack与queue的使用

本文链接: https://lsjlt.com/news/143845.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++ 超详细讲解stack与queue的使用
    目录stack介绍和使用模拟实现stack的使用例题最小栈栈的弹出压入序列逆波兰表达式求值queue模拟实现容器适配器deque简介priority_queue优先级队列priori...
    99+
    2024-04-02
  • C++stack与queue使用方法详细讲解
    目录Stack的介绍和使用stack的默认定义的模板stack的使用queue的介绍和使用queue的默认定义的模板queue的使用Stack的介绍和使用 stack的文档介绍 st...
    99+
    2023-01-04
    C++ stack与queue C++ stack使用方法 C++ queue使用方法
  • C++详细讲解stack与queue的模拟实现
    目录容器适配器双端队列概念结构deque迭代器优缺点stack模拟queue模拟实现容器适配器 适配器是一种设计模式(设计模式是一套反复使用的、大部分人知道的代码设计经验的总结),该...
    99+
    2024-04-02
  • C++超细致讲解队列queue的使用
    目录queue介绍queue常用函数1.常用函数2.函数运用示例queue介绍 只能访问 queue<T> 容器适配器的第一个和最后一个元素。只能在容器的末尾添加新元素,...
    99+
    2024-04-02
  • C++超详细讲解auto与nullptr的使用
    目录一. auto关键字1. auto介绍2. 使用规则3. auto不能推导的场景二. 基于范围的for循环(C++11)1. 范围for的语法2. 范围for的使用条件三. 指针...
    99+
    2024-04-02
  • Python队列Queue超详细讲解
    目录queue模块简介queue.Queue(maxsize=0)queue.LifoQueue(maxsize=0)queue.PriorityQueue(maxsize=0)qu...
    99+
    2023-05-16
    Python队列Queue Python Queue
  • c++中queue用法超详细讲解(入门必看!)
    目录1、queue的作用2、queue的定义3、queue的成员函数总结1、queue的作用 说到queue,大家一定会想到stack,同样是简单易用的数据结构之一。queue就是队...
    99+
    2022-11-13
    c++中queue的用法 c++ queue操作 C++ queue
  • C++ stack与queue模拟实现详解
    目录stack与queue模拟实现 stackqueue为什么选择deque作为stack和queue的底层默认容器总结stack与queue模拟实现 在stl中,stack(...
    99+
    2024-04-02
  • C++超详细讲解友元的使用
    目录一、友元的概念二、友元的用法三、友元的语法四、友元的尴尬五、注意事项六、小结一、友元的概念 什么是友元友元是 C++ 中的一种关系友元关系发生在函数与类之间或者类与类之间友元关系...
    99+
    2024-04-02
  • C++超详细讲解模板的使用
    目录一、函数模板1.1函数模板概念1.2 函数模板格式1.3 函数模板的原理1.4 函数模板的实例化二、类模板2.1 类模板的定义格式2.2类模板的实例化总结一、函数模板 1.1函数...
    99+
    2024-04-02
  • C语言超详细讲解宏与指针的使用
    目录1、关于define2、初识指针(1)内存(2)示例(3)指针的使用示例(4)指针变量的大小1、关于define define是一个预处理指令,有两种用法,一种是用define定...
    99+
    2024-04-02
  • C++ 超详细示例讲解list的使用
    目录一、list的介绍list的介绍二、list的使用2.1 list的构造函数2.2 list迭代器的使用2.3 list相关的容量大小相关的函数2.4 list数据的访问相关的函...
    99+
    2024-04-02
  • C++BoostLockfree超详细讲解使用方法
    目录一、说明二、示例和代码Boost.Lockfree 一、说明 Boost.Lockfree 提供线程安全和无锁容器。可以从多个线程访问此库中的容器,而无需同步访问。 在 1.56...
    99+
    2022-11-21
    C++ Boost Lockfree C++ Lockfree方案
  • C++BoostUuid超详细讲解
    目录一、说明二、Boost.Uuid库示例和代码一、说明 Boost.Uuid 为 UUID 提供生成器。 UUID 是不依赖于中央协调实例的通用唯一标识符。例如,没有数据库存储所有...
    99+
    2022-12-08
    C++ Boost Uuid C++ Uuid标识符
  • C++BoostUtility超详细讲解
    目录一、说明二、Boost.Utility库示例和代码一、说明 Boost.Utility 库是杂项、有用的类和函数的集合,它们太小而无法在独立库中维护。虽然实用程序很小并且可以快速...
    99+
    2022-12-08
    C++ Boost Utility C++ Utility库
  • PyTorch Dataset与DataLoader使用超详细讲解
    目录一、Dataset1. 在控制台进行操作①获取图片的基本信息②获取文件的基本信息2. 编写一个继承Dataset 的类加载数据①定义 MyData类②创建类的实例并调用二、Dat...
    99+
    2024-04-02
  • C语言超详细讲解指针的概念与使用
    目录一、指针与一维数组1. 指针与数组基础2. 指针与数组3. 一个思考二、指针与字符串三、指针和二维数组1. 指针数组与数组指针2. 指针数组3. 数组指针一、指针与一维数组 1....
    99+
    2024-04-02
  • C++线程安全容器stack和queue的使用详细介绍
    目录线程安全的容器栈threadsafe_stack线程安全的容器队列threadsafe_queue要构建线程安全的数据结构, 关注几点: 若某线程破坏了数据结构的不变量, 保证其...
    99+
    2024-04-02
  • C++超详细讲解树与二叉树
    目录树树的定义树的名词解释树的表示树的存储结构二叉树的概念及结构二叉树的概念二叉树的性质二叉树的存储结构顺序存储结构链式存储结构树 树的定义 Q:什么是树 A:树是一种 非线性 的数...
    99+
    2024-04-02
  • C++设计与声明超详细讲解
    目录让接口被正确使用不易被误用宁以pass-by-reference-to-const替换pass-by-value必须返回对象时将成员变量声明为private以non-member...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作