【C++】Stack和Queue

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:那个传说中的man的主页
🏠个人专栏:题目解析
🌎推荐文章:题目大解析3

在这里插入图片描述


👉🏻Stack Constructor

在这里插入图片描述
Stack官方文档

👉🏻Stack Member functions

在这里插入图片描述

👉🏻Queue constructor

在这里插入图片描述
Queue官方文档

👉🏻Queue Member functions

在这里插入图片描述

👉🏻Stack和Queue的模拟实现(模板实现)

#pragma once
#include <iostream>
#include <list>
#include <deque>
using namespace std;
namespace Space
{
	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();
		}
		bool empty()
		{
			return _con.empty();
		}
		size_t size()
		{
			return _con.size();
		}
	private:
		Container _con;

	};
	template <class T >
	class queque
	{
	public:
		void push(const T& x)
		{

			_lt.push_back(x);
		}
		void pop()//头删
		{
			_lt.pop_front();
		}
		T& front()
		{
			return _lt.front();
		}

		T& back()
		{

			return _lt.back();
		}
		bool empty()
		{
			return _lt.empty();
		}
		size_t size()
		{
			return _lt.size();
		}
	private:
		list<T> _lt;

	};

}

为什么deque可以作为栈和队列的适配容器?
下面引入deque。

👉🏻deque

C++中的deque(双端队列)是一种双向开口的动态数组,它可以在两端进行高效的插入和删除操作。deque具有以下的优点和缺点:

优点

  1. 高效的插入和删除操作:deque在首尾两端的插入和删除操作都非常高效,时间复杂度为O(1)。这使得它适用于需要频繁在两端插入或删除元素的场景。

  2. 随机访问:与vector类似,deque也支持随机访问,即可以通过索引快速访问任意位置的元素。由于其内部实现采用了分段连续存储的结构,使得索引访问的性能接近于数组。

  3. 动态扩展:deque可以根据需要动态地增加容量,而且当容量不够时,它的扩展方式比vector更加高效。deque内部由多个块组成,每个块的大小可以根据需求进行调整。

缺点

  1. 内存消耗:相比较于vectordeque在内存消耗上稍微更多。因为它需要维护多个块,并且每个块都需要额外的指针来链接。

  2. 迭代器失效:在对deque进行插入或删除操作后,迭代器可能会失效。这是因为deque为了保持两端的高效操作,可能会在中间插入或删除元素,导致原来的迭代器失去指向有效元素的能力。需要注意及时更新迭代器。

综上所述,deque在双端的插入和删除操作上具有较高的效率,并支持随机访问,适用于需要频繁操作头尾的场景。然而,需要注意内存消耗和迭代器失效的问题。根据实际需求选择合适的容器是很重要的。


deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维
数组,其底层结构如下图所示
在这里插入图片描述
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落
在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:
在这里插入图片描述
为什么选择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的优点,而完美的避开了其缺陷。

👉🏻优先级队列——priority_queue

C++ 的 priority_queue 是一个数据结构,它是一个优先级队列,可以维护一组元素并且每次取出的都是当前最大(或最小)的元素。priority_queue 内部实现方式是使用堆 (heap),一种特殊的树形数据结构,具有以下特性:

  1. 堆是一个完全二叉树(fully binary tree),即除了最后一层之外,其他的层必须有节点,在最后一层上,所有的节点都集中在左边;
  2. 堆分为最大堆和最小堆两种类型,其中最大堆的根节点的值最大,最小堆的节点的值最小,priority_queue 默认是最大堆;
  3. 任何一个节点的值都大于其左子树和右子树的值,这条性质被称为堆属性。

在 C++ 中,priority_queue 是一个模板类,定义在 queue 头文件中。以下是一些 priority_queue 常用操作:

  1. push(x) :将元素 x 加入到队列中;
  2. pop() :将队列中最大的元素弹出;
  3. top() :获取队列中最大的元素,但不弹出;
  4. empty() :判断队列是否为空;
  5. size() :返回队列内元素的个数。

优先队列的元素可以是基本数据类型和结构体类型,但是,在存储复杂对象时,需要自定义比较器,用来告诉程序如何比较这些对象的大小。

以下是一个示例,演示了如何使用 priority_queue 实现获取前k个大的元素:

#include <bits/stdc++.h>
using namespace std;

int main() {
    int k = 5;
    vector<int> nums = {2, 7, 9, 1, 5, 8, 3, 6, 10, 4};

    // 定义一个最大堆
    priority_queue<int, vector<int>, less<int>> q;//less其实不用写,因为默认就是大堆

    // 将前K个元素加入到堆中
    for (int i = 0; i < k; ++i) {
        q.push(nums[i]);
    }

    // 遍历剩下的元素
    for (int i = k; i < nums.size(); ++i) {
        if (q.top() > nums[i]) {
            q.pop();
            q.push(nums[i]);
        }
    }

    // 输出前k个大的元素
    while (!q.empty()) {
        cout << q.top() << " ";
        q.pop();
    }

    return 0;
}

运行结果为:10 9 8 7 6
如果创建小堆,将第三个模板参数换成greater比较方式

priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());

priority_queue模拟实现

code.h:

#pragma once
#include <iostream>
#include <vector>
using namespace std;
namespace Space
{
	template <class T>
	class Greater
	{
	public:
		bool operator()(int a, int b)//仿函数,运算符重载();意义:替代函数指针
		{
			return a > b;
		}
	};

		
	template <class T,class Container = vector<T>,class Compare = less<int>>//less如果改为Greatet就是建小堆了
	class priority_queue
	{
	public:
		priority_queue()
		{

		}
		template <class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first, last)
		{
			//向下调整建堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)//从尾部的第一个父节点开始
			{
				adjust_down(i);
			}

		}
		void adjust_up(int child)
		{
			int parent = (child - 1) / 2;
			while (child)
			{
				Compare com;
				if(com(_con[parent] , _con[child]))
				/*if (  _con[parent]< _con[child])*/
				{
					swap(_con[child], _con[parent]);
					//更新一下child和parent的位置
					child = parent;
					parent = (parent - 1) / 2;
				}
				else
					break;
			}
		}
		void adjust_down(int parent)
		{
			Compare com;
			int child = 2 * parent + 1;//先确定左孩子
			while (child < _con.size())
			{
				//判断左孩子和右孩子哪个最大,然后选出最大的和父比较
				if (child + 1 < _con.size() &&  com(_con[child],_con[child + 1]) )
				{
					child++;
				}
				if (com(_con[parent] , _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = 2 * parent + 1;
				}
				else
					break;
			}
		}
		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}
		void pop()
		{
			//先首尾交换数据->再删除尾部数据->再向下调整
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjust_down(0);
		}
		T top()
		{
			return _con[0];
		}
		bool empty()
		{

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

	void Priority_test1()
	{
		int k = 5;
		vector<int> nums = { 2, 7, 9, 1, 5, 8, 3, 6, 10, 4 };
		Space::priority_queue<int> pq(nums.begin(),nums.end());
		/*for (int i = 0; i < nums.size(); i++)
		{
			pq.push(nums[i]);
		}*/

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

test result
在这里插入图片描述

👉🏻LeetCode题

最小栈

最小栈

class MinStack {
public:
    MinStack() 
    {
        //不需要写构造函数
        //自定义类型会去调用它自己的默认构造函数
    }
    
    void push(int val) {
        _st.push(val);
         if(_minst.empty()||val<=_minst.top())
         {
             _minst.push(val);
         }
    }
    void pop() {
        
         //如果_st的栈顶等于_minst的栈顶,_minst也要出栈
         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;
};

那么如果在st中插入很多重复的数据,我们还要再向_minst中插入吗?
其实我们这里可以构建一个结构体

struct CountVal
		{
			int _val;
			int count = 0;
		};

我们插入一个结构体,遇到相同的++count 即可

栈的压入、弹出序列

原题链接栈的压入、弹出序列
MyCode:

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pushV int整型vector 
     * @param popV int整型vector 
     * @return bool布尔型
     */
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
        stack<int>  st;
        size_t popV_sz = popV.size();
        size_t pushV_sz = pushV.size();
        int i = 0, j = 0;
      
            int flag = 0;
            for(i=0,j =0;j<pushV_sz;j++)
            {
                //先从栈里面先找
                while(!st.empty())
                {
                    if(st.top()==popV[i])
                  {
                       i++;
                    flag = 1;//找到了
                     //找到后记得pop掉
                    st.pop();
                  }
                  else 
                  {
                      break;
                  }
                   
                }
                if(popV[i] == pushV[j])
                 {
                    i++;
                    flag = 1;//找到了
                 }
                 else
                {
                      st.push(pushV[j]);
                }
                 
            }
            if(!flag)
            return false;//如果结束都找不到
        //接下来进入出栈匹配环节
        while(!st.empty())
        {
             if(popV[i++]!=st.top())
             return false;
             st.pop();
        }
        return true;
      

    }
};

思路思想
在这里插入图片描述

其实后续发现无需flag,只要最后出栈环节匹配不上也不对。
所以优化掉flag后

class Solution {
public:
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
        stack<int>  st;
        size_t popV_sz = popV.size();
        size_t pushV_sz = pushV.size();
        int i = 0, j = 0;
      
            for(i=0,j =0;j<pushV_sz;j++)
            {
                //先从栈里面先找
                while(!st.empty())
                {
                    if(st.top()==popV[i])
                  {
                       i++;
                     //找到后记得pop掉
                    st.pop();
                  }
                  else 
                  {
                      break;
                  }
                   
                }
                if(popV[i] == pushV[j])
                 {
                    i++;
                 }
                 else
                {
                      st.push(pushV[j]);
                }
                 
            }
        //接下来进入出栈匹配环节
        while(!st.empty())
        {
             if(popV[i++]!=st.top())
             return false;
             st.pop();
        }
        return true;
    }
};

other version:
在这里插入图片描述

bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
      stack<int> st;
        int pushi = 0, popi = 0;
        while (pushi < pushV.size())
        {
            //先入栈
            st.push(pushV[pushi++]);
            while (!st.empty() && st.top() == popV[popi])
            {
                popi++;
                st.pop();
            }
        }
        return st.empty();
    }
};

数组中的第K个最大元素(优先级队列秒杀)

原题链接:数组中的第K个最大元素

mycode:

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int> pq(nums.begin(),nums.end());
        while(--k)
        {
            pq.pop();
        }
        return pq.top();
    }
};

如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值