C++:stack和queue的使用以及底层实现

news2025/1/24 8:37:30

stack和queue的使用以及底层实现

    • 1.适配器模式
    • 2.stack的介绍和使用
      • 2.1stack的介绍
      • 2.2stack的使用
    • 3.queue的介绍和使用
      • 3.1queue的介绍
      • 3.2queue的使用
    • 4.仿函数介绍
    • 5.priority_queue的介绍和使用
      • 5.1priority_queue的介绍
      • 5.2priority_queue的使用
    • 6.deque的介绍
      • 6.1deque的实现原理
      • 6.2deque的缺陷
      • 6.3选择deque作为stack和queue的底层默认容器的原因
    • 7.模拟实现
      • 7.1stack的模拟实现
      • 7.2queue的模拟实现
      • 7.3priority_queue的模拟实现

1.适配器模式

适配器是一种设计模式(代码设计经验),该种模式是把一个类的接口转换成用户希望的另一个接口。像本文介绍的容器底层其实是用其它容器实现的,提供给用户的接口只是对其它容器接口的简单封装


2.stack的介绍和使用

2.1stack的介绍

  1. stack是栈。

  2. stack是一种容器适配器,特点是后进先出,其删除只能从容器的一端进行元素的插入与提取操作

  3. stack是一个类模板(template <class T, class Container = deque<T> > class stack),stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
    (1)empty:判空操作
    (2)back:获取尾部元素操作
    (3)push_back:尾部插入元素操作
    (4)pop_back:尾部删除元素操作

  4. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque(双端队列,后面再讲)。

  5. 使用要包含头文件<stack>。
    在这里插入图片描述

2.2stack的使用

常用接口说明:

函数说明
stack()构造函数,构造空栈
empty()判断栈是否为空,空返回真,否则返回假
size()返回栈中的元素个数
top()取到栈顶数据的引用
push(val)把元素val压入栈中
pop()弹出栈顶元素

练习:
最小栈

//这个题目的要点是记录最小值,但出栈后可能最小值可能变化,只用一个变量记录不够
//我们可以设计两个栈:
//(1)s栈,正常出入数据
//(2)min栈,在s栈入栈时进行判断,如果自身为空或者新入栈的元素小于等于min栈顶就入栈
//在s栈出栈时也进行判断,如果出栈的元素等于min栈顶,min也要出栈

//不过这个题目还有优化的空间,那就是[1,1,1,1,1]这样多个相同数的情况
//为避免空间浪费,可以设计一个count计数记录每个数,多次出现的数入、出栈只需要减计数
//计数归0才真正的出栈
class MinStack {
public:
    MinStack() 
    {}
    
    void push(int val) 
    {
        if(min.empty() || min.top() >= val)
        {
            min.push(val);
        }
        s.push(val);
    }
    
    void pop() 
    {
        if(s.top() == min.top())
        {
            min.pop();
        }
        s.pop();
    }
    
    int top() 
    {
        return s.top();
    }
    
    int getMin() 
    {
        return min.top();
    }
private:
    stack<int> s;
    stack<int> min;
};

栈的弹出压入序列

//这个题目最好的办法就是模拟栈的压入弹出过程
//比如pushv[1,2,3,4,5]这个序列得到popv[4,5,3,2,1]
//先入栈s,1,1 != popV.top(),继续入栈
//入栈s,2, 2 != popV.top(),继续入栈
//入栈s,3,3 != popV.top(),继续入栈
//入栈s,4, 4 == popV.top(),同时出栈,popV是一个数组,下标加1视为出栈
//出栈结束栈s.top() = 3 != popV.top() = 5,继续入栈
//入栈, 5, 5 == popV.top(),同时出栈
//出栈结束s.top() = 3 == popV.top() = 3,同时出栈
//………………………………………………………………………………
//最后pushV的所有元素都入栈,并且s栈刚好出空,说明pushV可以得到popV
//如果pushV所有元素入栈,s栈没法出空,说明pushV无法得到popV
class Solution {
public:
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) 
    {
        size_t pushi = 0;
        //popi下标加1视为popV出栈
        size_t popi = 0;
        stack<int> s;

        while (pushi < pushV.size()) 
        {
            s.push(pushV[pushi++]);
            while(!s.empty() && s.top() == popV[popi])
            {
                s.pop();
                popi++;
            }
        }

        return s.empty();
    }
};

逆波兰表达式求值

//这个题目思路并不难,借助一个栈s即可
//(1)遇到数字:直接入s栈
//(2)遇到运算符,把栈中的两个左右数出栈,计算完把结果入s栈即可
//重复上面的步骤,一直到遍历完tokens即可,最后s栈顶即为结果
class Solution {
public:
    int evalRPN(vector<string>& tokens) 
    {
        stack<int> s;
        for(auto str : tokens)
        {
            if(!(str == "+" || str == "-" || str == "*" || str == "/"))
            {
               s.push(atoi(str.c_str()));
            }
            else
            {
                int right = s.top();  s.pop();
                int left = s.top();  s.pop();
                switch(str[0])
                {
                    case '+':
                        s.push(left + right);
                        break;
                    case '-':
                        s.push(left - right);
                        break;
                    case '*':
                        s.push(left * right);
                        break;
                    case '/':
                        s.push(left / right);
                        break;
                }
            }
        }
        return s.top();
    }
};

3.queue的介绍和使用

3.1queue的介绍

  1. queue是队列。

  2. queue是一种容器适配器,特点为先进先出,其中从容器一端插入元素,另一端提取元素。

  3. queue是一个类模板(template <class T, class Container = deque<T> > class queue;),底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
    (1)empty:检测队列是否为空
    (2)size:返回队列中有效元素的个数
    (3)front:返回队头元素的引用
    (4)back:返回队尾元素的引用
    (5)push_back:在队列尾部入队列
    (6)pop_front:在队列头部出队列

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

  5. 使用要包含头文件<queue>。

在这里插入图片描述

3.2queue的使用

常用接口说明:

函数说明
queue()构造函数,构造一个空队列
empty()判断队列是否为空,空返回真,否则返回假
size()返回队列的元素个数
front()返回队头元素的引用
back()返回队尾元素的引用
push(val)队尾入元素val
pop()队头元素出队

练习:
用队列实现栈

//这个题目的思路是有两个队列实现栈(其实也可以单队列实现,这里主要不是讲题目)
//需要始终有一个队列为空,比如[1,2,3]这个顺序队列
//队列q1为1 -> 2 - > 3,q2为空,top()只需要返回不为空的队列的队尾即可
//难点在出栈,出栈的话把1 -> 2转移到另一个队列q2,剩余一个元素即为栈顶,保存了再出栈即可
class MyStack {
public:
    MyStack() {}
    
    void push(int x) {
        if(q2.empty())
            q1.push(x);
        else
            q2.push(x);
    }
    
    int pop() {
        //默认q1为空栈
        queue<int>* empty_q = &q1;
        queue<int>* non_empty_q = &q2;
        if(q2.empty())
        {
            empty_q = &q2;
            non_empty_q = &q1;
        }
        while(non_empty_q->size() > 1)
        {
            empty_q->push(non_empty_q->front());
            non_empty_q->pop();
        }
        int ret = non_empty_q->front();
        non_empty_q->pop();
        return ret;
    }
    
    int top() {
        if(q1.empty())
            return q2.back();
        else
            return q1.back();
    }
    
    bool empty() {
        return q1.empty() && q2.empty();
    }
    queue<int> q1;
    queue<int> q2;
};

4.仿函数介绍

后面需要用到,就先简单介绍一下,以后会详细的讲各种用途。

//仿函数,又称函数对象,其实就是一个类,重载了operator()
// 使得类对象可以像函数一样使用
// 相比传函数指针,传仿函数要更加灵活一些
//开一个命名空间,和库里面的区分开
namespace my_compare
{
	template<class T>
	class less
	{
	public:
		bool operator()(const T& x, const T& y)const
		{
			return x < y;
		}
	};

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

5.priority_queue的介绍和使用

5.1priority_queue的介绍

  1. priority_queue(优先级队列)是堆。

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

  3. 在优先级队列中可以随时插入元素,并且只能检索最大(小)元素(优先队列中位于顶部的元素)

  4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该支持随机访问(下标加[]),并支持以下操作:
    (1)empty():检测容器是否为空
    (2)size():返回容器中有效元素个数
    (3)front():返回容器中第一个元素的引用
    (4)push_back():在容器尾部插入元素
    (5)pop_back():删除容器尾部元素

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

  6. 需要支持随机访问,以便始终在内部保持堆结构。

  7. priority_queue是一个模板类,template <class T, class Container = vector<T>, class Compare = less< typename Container::value_type>> class priority_queue;

  8. 使用前要包含<queue>头文件

5.2priority_queue的使用

常用接口说明:

函数说明
priority_queue()构造一个空的优先级队列
priority_queue(begin(), end())传迭代器区间初始化
empty()判断优先级队列是否为空,空返回真,否则返回假
top()返回优先级队列中最大(小)元素,即堆顶元素
push(val)在优先级队列中插入元素val
pop()删除优选级队列中最大(小)元素,即堆顶元素

注意:

  1. 默认情况下priority_queue为大堆。
#include <vector>
#include <queue>
#include <functional> // greater算法的头文件
void TestPriorityQueue()
{
	 // 默认情况下,创建的是大堆,其底层按照小于号比较
	 vector<int> v{3,2,7,6,0,4,1,9,8,5};
	 priority_queue<int> q1;
	 for (auto& e : v)
	 	q1.push(e);
	 cout << q1.top() << endl;
	 // 如果要创建小堆,将第三个模板参数换成greater比较方式
	 priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
	 cout << q2.top() << endl;
}
  1. 如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。

练习:

数组中的第K个最大元素

//对于取数组中前K个最大的元素(TOPK),很容易就能想到利用堆
//先把原数组构造一个堆,要求第K大的元素
//只需要pop() K - 1次即可,最后堆顶一定是第k个大的元素
//其中建堆的时间复杂度为O(N),K - 1次的删除后调整为 (K-1) * logN
//在 K比较小的情况下,时间复杂度接近于O(N)
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k)
    {
        //其实这个题目也可以利用快速排序,不过必须选择性的排序区间
        priority_queue<int> p(nums.begin(), nums.end());
        for(int i = 0; i < k - 1; i++)
        {
            p.pop();
        }
        return p.top();
    }
};

6.deque的介绍

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

6.1deque的实现原理

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

在这里插入图片描述

6.2deque的缺陷

  1. 不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list。deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构
    (实在要使用的话包含头文件< deque >)
  2. 头插尾插入效率高,中间插入还是要移动数据的(子数组长度恒定),并且频繁的下标加[]访问有很大的计算消耗,这个时候效率远远不如vector和list。

6.3选择deque作为stack和queue的底层默认容器的原因

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

7.模拟实现

7.1stack的模拟实现

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

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

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

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

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

	private:
		container _con;
	};
}

7.2queue的模拟实现

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

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

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

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

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

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

	private:
		container _con;
	};
}

7.3priority_queue的模拟实现

本文主要讲的不是堆相关的算法,如果大家对建堆和调整有疑问,可以看往期的堆讲解:
堆的实现
堆实际应用和时间复杂度分析

namespace my_std
{
	//优先级队列
	template<class T, class container = vector<T>, class comparation = less<T>>
	class priority_queue
	{
	public:
		//向上调整
		void adjust_up(size_t child)
		{
			comparation com; //用于比较的仿函数
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				// _con[parent]  < _con[child]
				//默认大堆,如果孩子大,就交换和父亲的值
				//然后更新孩子和父亲的下标
				if (com(_con[parent], _con[child]))
				{
					std::swap(_con[parent], _con[child]);
					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)
		{
			size_t child = parent * 2 + 1;
			comparation com;
			while (child < _con.size())
			{
				//_con[child] < _con[child + 1]
				//默认左孩子大,如果右孩子大就让孩子下标加1,注意右孩子不存在的情况
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					child++;
				}
				//_con[parent] < _con[child]
				//默认大堆。如果孩子大就和父亲交换,然后更新孩子下标和父亲下标
				if (com(_con[parent], _con[child]))
				{
					std::swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;	 
				}
				else
				{
					break;
				}
			}
		}

		void pop()
		{
			std::swap(_con.front(), _con.back());
			_con.pop_back();
			adjust_down(0);
		}


		//无参构造,这个必须写,不然就没有默认构造用了
		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--)
			{
				adjust_down(i);
			}
		}

		
		/// ///
		
		T& top()
		{
			return _con.front();
		}

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

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

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

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

相关文章

Java从入门到精通-数组(一)

0.思维导图 1.数组概述 • 1.1 数组的定义 在编程中&#xff0c;数组是一种用于存储多个相同类型的数据元素的数据结构。数组提供了一种有序的方式来组织和访问数据&#xff0c;每个元素都有一个唯一的索引&#xff0c;通过索引可以快速访问元素。 • 1.2 数组的重要性 数据…

状态管理艺术——借助Spring StateMachine驭服复杂应用逻辑

文章目录 1. 什么是状态2. 有限状态机概述3. Spring StateMachine4. Spring StateMachine 入门小案例4.1 接口测试 5. 总结 1. 什么是状态 在开发中&#xff0c;无时无刻离不开状态的一个概念&#xff0c;任何一条数据都有属于它的状态。 比如一个电商平台&#xff0c;一个订…

自动化测试的重要性:为何追求自动化?

为什么需要自动化测试&#xff1f; 代替手工重复操作&#xff0c;测试工程师可以花更多时间在设计全面的测试用例和新功能测试上 【代替手工重复】 提升回归测试的效率&#xff0c;适合敏捷开发过程 【提升回归效率】 更好的利用非工作时间执行测试&#xff0c;工作时间分析失…

油猴浏览器(安卓)

油猴浏览器页面设计非常简约&#xff0c;在主页上还为小伙伴们推荐了很多的常用书签&#xff0c;像油猴脚本&#xff0c;常用导航&#xff0c;新闻&#xff0c;热搜类的&#xff0c;快递查询等等&#xff0c;可以设置快捷访问&#xff0c;把常用到的一些网站设置在主页上。 浏览…

代码随想录Day_60打卡

①、柱状图中最大的矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 事例&#xff1a; 输入&#xff1a;heights [2,1,5,6,2,3] 输出&#xff1a;1…

Chrome 117 发布:新 Web 开发调试技巧都在这了!

简介&#xff1a;Chrome 更新了最新版本 Chrome 117&#xff0c;更新了很多实用的DevTools 新特性。 首先介绍大家最熟悉的Network面板&#xff0c;看看给我们带来了什么好玩的~ Network 面板改进 更快地在本地覆盖网页内容 现在&#xff0c;本地覆盖功能更加简化&#xff0…

【数据结构】二叉树基础入门

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Alibaba(商品详情)API接口

为了进行电商平台 的API开发&#xff0c;首先我们需要做下面几件事情。 1&#xff09;开发者注册一个账号 2&#xff09;然后为每个alibaba应用注册一个应用程序键&#xff08;App Key) 。 3&#xff09;下载alibaba API的SDK并掌握基本的API基础知识和调用 4&#xff09;利…

解锁“高级特权” 偷偷用。千万别声张

现在的手机修图软件&#xff0c;功能是越来越强大了。之前需要用到电脑端PS的功能&#xff0c;现在手机上的修图软件就能实现&#xff0c;还是一键处理的傻瓜操作。 你离P图大神只差一个APP——「大神批图」&#xff0c;已解锁限制&#xff0c;安装即是VIP。 功能非常丰富&…

多元共进|创新技术提供助力,共创增长机遇

谷歌致力于推动业务和应用长效增长 助力开发者优化用户体验 一起来了解 2023 Google 开发者大会上 谷歌如何将创新技术 融入商业合作和智能家居生态 用技术赋能业务增长 以科技点亮智慧生活 谷歌坚持以 AI 为技术核心&#xff0c;不断投入研究&#xff0c;并将其应用至各类场景…

【C++刷题】经典简单题第二辑

回文排列 class Solution { public:bool canPermutePalindrome(string s) {// 记录字符出现的次数int count[256] {0};for(size_t i 0; i < s.size(); i)count[s[i]];// 记录字符出现次数为奇数的个数int flag 0;for(size_t i 0; i < 256; i)if(count[i] % 2 1)f…

JavaScript作用域链与预解析

查找上一级 JavaScript的预解析 js解析器在运行js代码时会先进行预解析,再进行代码的执行 预解析时js引擎会把js里面所有的var还有function提升到当前作用域的最前面 代码执行,按照代码书写的顺序从上往下执行 预解析分为变量预解析(变量提升)与函数预解析(函数提升) 1.变量…

优秀的 Modbus 主站(主机、客户端)仿真器、串口调试工具

文章目录 优秀的 Modbus 主站&#xff08;主机、客户端&#xff09;仿真器、串口调试工具主要功能软件截图 优秀的 Modbus 主站&#xff08;主机、客户端&#xff09;仿真器、串口调试工具 modbus master,modbus,串口,工控,物联网,PLC,嵌入式 官网下载地址&#xff1a;http:/…

七)Stable Diffussion使用教程:附加功能

图生图右侧有个附加功能选项,里面其实也存在一个图片放大(缩放)功能,而且因为它不涉及重绘的过程,所以不需要任何提示词,适用于任何图片(包括非 SD 生成的图片)。 原理:在拉伸放大的基础上适当对色块和线条的边缘做了模糊处理,和其他工具的放大原理类似。 观察界面…

【LeetCode-中等题】367. 有效的完全平方数

文章目录 题目方法一&#xff1a;二分查找 题目 方法一&#xff1a;二分查找 找 1 - num 之间的 mid&#xff0c; 开方是整数 就找得到 mid&#xff0c; 不是整数自然找不到mid class Solution { // 二分查找 &#xff1b;找 1 - num 之间的mid 开方是整数 就找得到 不是…

python超详细安装

目录 初始python获取python安装包python解释器安装pycharm编译器安装pycharm的简单使用&#xff08;第一个hello world&#xff09; 初始python Python 是一款易于学习且功能强大的编程语言。 它具有高效率的数据结构&#xff0c;能够简单又有效地实现面向对象编程。 Python简…

平衡二叉搜索树(AVL)——【C++实现插入、删除等操作】

本章完整代码gitee地址&#xff1a;平衡二叉搜索树 文章目录 &#x1f333;0. 前言&#x1f332;1. AVL树概念&#x1f334;2. 实现AVL树&#x1f33f;2.1 结构定义&#x1f33f;2.2 插入&#x1f490;左单旋&#x1f490;右单旋&#x1f490;左右双旋&#x1f490;右左双旋 &a…

c++day1

练习&#xff1a;使用cout完成输出斐波那契前20项的内容 1 1 2 3 5 8 13.。。。 #include <iostream> using namespace std;int main() {int a[20]{1,1};for(int i2;i<20;i){a[i]a[i-1]a[i-2];}for(int i0;i<20;i){cout<<a[i]<<" ";}retur…

javascript【格式化时间日期】

javascript【格式化时间日期】 操作&#xff1a; (1) 日期格式化代码 /*** 日期格式化函数<br/>* 调用格式&#xff1a;需要使用日期对象调用* <p> new Date().Format("yyyy/MM/dd HH:mm:ss"); </p>* param fmt 日期格式* returns {*} 返回格式化…

易优cms响应式月嫂家政服务公司网站模板源码—自适应手机端设计,支持后台管理

易优cms响应式月嫂家政服务公司网站模板源码 自适应手机端 带后台 模板基于EyouCMS内核制作,模板编码为UTF8 ,适合行业:家政服务类企业。 模板信息&#xff1a; 模板分类&#xff1a;摄像、婚庆、家政、保洁 适合行业&#xff1a;家政服务类企业 模板介绍&#xff1a; 本模…