C++语言基础——stack和queue

学习完容器,来看看容器适配器。容器适配器也是STL的组成部分。

目录

1. deque

1.1 概念

1.2 迭代器

1.3 特点

1.4 总结

2. 容器适配器

2.1 stack

2.1.1 概念

2.1.2 接口

2.1.3 应用

2.1.4 实现

2.2 queue

2.2.1 概念

2.2.2 接口

2.2.3 应用

2.2.4 实现

2.3 priority_queue

2.3.1 概念

2.3.2 接口

2.3.3 运用

2.3.4 细节

1. deque

1.1 概念

在说容器适配器之前,先来说下双端队列,双端队列也是一个容器。

名字是双端队列,但本质可以理解为一个二维数组。双端队列由中控和buff组成。

1.2 迭代器

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”的假象,落在了deque的 迭代器身上。deque的迭代器是两个迭代器,一个start迭代器,一个finish迭代器。类似于begin和end。

1.3 特点

它综合了list和vector,其特点如下

  • 支持随机访问
  • 插入和删除的时间复杂度是O(1)
  • 开辟空间代价小,不会造成空间碎片

1.4 总结

deque这个容器不常用,因为虽然综合了vector和list,但是deque赶不上vector和list优势:比如它没有vector的访问效率高。它的接口非常多,毕竟是前两者的综合,由于不常用,所以这里了解原理就好。

2. 容器适配器

容器适配器是一种设计模式,该模式是将一个类的接口转换成用户希望的另外一个接口。例如:

stack(栈)、queue(队列)、priority_queue(优先级队列)都是容器适配器。因为它们在底层只是对其他容器进行了封装,就行接口转换器一样,只是将其他接口转换了一下,而我们虽然用的是适配器,其实底层还是容器。下面来深入理解。

2.1 stack

2.1.1 概念

在数据结构阶段,就接触到了栈(点击进入)。这里是容器适配器,但是特性不变。栈是先进后出的。而且删除插入等操作都只能在栈顶操作,

2.1.2 接口

在c++里,有以下常用接口。

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

2.1.3 应用

(1)设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。

  • push(x) -- 将元素 x 推入栈中。
  • pop() -- 删除栈顶的元素。
  • top() -- 获取栈顶元素。
  • getMin() -- 检索栈中的最小元素。

class MinStack {
public:
	MinStack() {}
	void push(int x) {
		if (_minStack.empty() || _minStack.top >= x) {
			_minStack.push(x);
		}
		_mainStack.push(x);
	}
	void pop() {
		if (_mainStack.top() == _minStack.top()) {
			_minStack.pop();
		}
		_mainStack.pop();
	}
	int top()  {
		return _mainStack.top();
	}
	int getMin() {
		return _minStack.top();
	}
private:
	stack<int> _mainStack;
	stack<int> _minStack;
};

(2)栈的弹出压入序列

  • 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1, 2, 3, 4, 5是某栈的压入顺序,序列4, 5, 3, 2, 1是该压栈序列对应的一个弹出序列,但4, 3, 5, 1, 2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
class Solution2 {
public:
    bool isPopOrder(vector<int> pushV, vector<int> popV) {
        if (pushV.size() != popV.size()) {
            return false;
        }
        int index = 0; // 记录pushV的下标
        int outdex = 0; // 记录popV的下标

        stack<int> test; // 创建一个栈来进行入栈出栈
        int inSize = pushV.size();
        int outSize = popV.size();

        while (outdex < out_size) { // 如果popV走完了,就说明是一个出栈顺序。
            // 如果栈还是空的我们就先入栈
            // 或者栈顶元素不等于我们的popV,就进行入栈
            while (test.empty() || test.top() != popV[outdex]) {
                //如果pushV走完了,test不是空栈,说明这不是一个出栈顺序
                if (index < inSize) {
                    test.push(pushV[index++]);
                } else {
                    return false;
                }
            }
            ++outdex;
            test.pop();
        }
        return true;
    }
};

(3)使用栈实现队列的下列操作

  • push(x) -- 将一个元素放入队列的尾部。
  • pop() -- 从队列首部移除元素。
  • peek() -- 返回队列首部的元素。
  • empty() -- 返回队列是否为空。
class Myqueen {
public:
	Myqueen() {}
	void push(int x) {
		_tailStack.push(x);
	}
	int pop() {
		if (!_frontStack.empty()) {
			int tmp = _frontStack.top();
			_frontStack.pop();
			return tmp;
		} else {
			while (!_tailStack.empty()) {
				_frontStack.push(_tailStack.top());
				_tailStack.pop();
			}
			if (_frontStack.empty()) {
				return false;
			} else {
				int tmp = _frontStack.top();
				_frontStack.pop();
				return tmp;
			}
		}
	}
	int top() {
		if (!_frontStack.empty()) {
			
			return _frontStack.top();
		} else {
			while (!_tailStack.empty()) {
				_frontStack.push(_tailStack.top());
				_tailStack.pop();
			}
			if (_frontStack.empty()) {
				return false;
			} else {
				return _frontStack.top();
			}
		}
	}
private:
	stack<int> _tailStack;
	stack<int> _frontStack;
};

2.1.4 实现

现在来说一说stack是怎么实现的,因为stack是一种适配器,所以底层只要用其他容器的接口就行。stack只能在栈顶操作,所以只要支持下面操作接口的容器就能实现一个栈

  • push_back():尾插
  • pop_back():尾删
  • back():获取尾部元素
  • empty():判断是否为空

那么有哪些容器呢?就是下面的这些容器,因为它们都支持上述的接口

  • deque(默认的,栈是不需要随机访问的,它的扩容的代价小,不会造成内存碎片)
  • vector
  • list
template<class T,class containser = deque<T>>

class Stack {
public:
	void Push(const T& val) {
		_con.push_back();
	}
	void Pop() {
		_con.pop();
	}
	bool Empty() {
		return _con.empty();
	}
	T& top() {
		return _con.back();
	}
private:
	containser _con;
};

2.2 queue

2.2.1 概念

 数据结构阶段,也接触过。(队列点击进入)队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。

2.2.2 接口

queue的使用
接口声明接口说明
queue (const container_type& ctnr = container_type())构造空的队列
bool empty() const检测队列是否为空,是返回true,否则 返回false
size_type size() const返回队列中有效元素的个数
value_type& front()返回队头元素的引用
const value_type& front() const返回队头元素的const引用
value_type& back()返回队尾元素的引用
const value_type& back() const返回队尾元素的cosnt引用
void push(value_type& val)在队尾将元素val入队列
void pop()将队头元素出队列

2.2.3 应用

  • 给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
struct TreeNode {
     int val;
     TreeNode *left;
     TreeNode *right;
     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 };
 
class Solution {
public:
	int treeHeight(TreeNode* root) {
		if (!root) {
			return 0;
		}
		int left = treeHeight(root->left);
		int right = treeHeight(root->right);

		return left > right ? left + 1 : right + 1;
	}
	vector<vector<int>> levelOrder(TreeNode* root) {
		int height = treeHeight(root);
		vector<vector<int>> treeVec;
		treeVec.resize(height);
		queue<TreeNode*> Pnode;
		queue<int> Index;
		TreeNode* tmp = root;
		if (!tmp) {
			return treeVec;
		}
		Pnode.push(tmp);
		Index.push(0);
		while (!Pnode.empty()) {
			tmp = Pnode.front();
			Pnode.pop();
			int index = Index.front();
			Index.pop();
			treeVec[index].push_back(tmp->val);
			if (tmp->left)
			{
				Pnode.push(tmp->left);
				Index.push(index + 1);
			}
			if (tmp->right)
			{
				Pnode.push(tmp->right);
				Index.push(index + 1);
			}
		}
		return treeVec;
	}
};

2.2.4 实现

queue同stack一样,都是适配器,所以只要有容器满足下面的接口,同样实现方式。

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

但是它不能用vector。因为vector实现头插效率太低,而且也没有push_front、pop_front()等操作

  • deque(默认的,队列是不需要随机访问的,它的扩容的代价小,不会造成内存碎片)
  • list
template<class T, class containser = deque<T>>
class Queue
{
public:
	void Push(const T& val)
	{
		con_.push_back(val);
	}
	void Pop()
	{
		con_.pop_front();
	}
	T& Front()
	{
		return con_.front();
	}
	T& Back()
	{
		return con_.back();
	}
	bool Emtpy()
	{
		return con_.empty();
	}
private:
	containser con_;
};

2.3 priority_queue

2.3.1 概念

优先队列是一种容器适配器,它在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。 默认情况下priority_queue是大堆。在前面数据结构中学到过堆(点击进入-复习)。

2.3.2 接口

ptiority_queue的使用

接口声明                                                                              

接口说明
priority_queue(const Compare& x = Compare(),  const Container& y = Container() );构造一个空的优先 级队列
priority_queue(InputIterator first, InputIterator last, const Compare& comp = Compare(), const Container& ctnr = Container());用[first, last)区间 中的元素构造优先级队列
bool empty( ) const检测优先级队列是 否为空,
const value_type& top ( ) const返回优先级队列中 最大(最小元素), 即堆顶元素
void push ( const T& x )在优先级队列中插 入元素x
void pop ( )删除优先级队列中 最大(最小)元素即堆顶元素

2.3.3 运用

  • 在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
class Solution { 
public:    
    int findKthLargest(vector<int>& nums, int k)
     {        // 将数组中的元素先放入优先级队列中        
            priority_queue<int> p(nums.begin(), nums.end());
             // 将优先级队列中前k-1个元素删除掉        
            for(int i= 0; i < k-1; ++i)     
           {          
              p.pop();    
           }           
             return p.top(); 
       }
 };
 

2.3.4 细节

  • 优先级队列是堆,而且默认是大堆
  • 如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。
  • 有些情况下,用户可能需要提供比较器规则
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值