在阅读《算法导论》基本数据结构那一章时,看到了练习10.1-6 要求用两个栈来实现队列,于是好奇为什么会存在这样一种需求,在上网查阅了相关资料后,将收获整理如下。其中参考来源主要是https://stackoverflow.com/questions/7395400/why-do-we-do-implement-a-queue-using-2-stacks, stackoverflow的一个问题

一、如何用两个栈来实现队列

算法基本思路:定义两个栈,不妨称为StackIn和StackOut,当接收到入队的请求时,将元素压入StackIn,接收到出队请求时,先检测StackOut是否为空,不为空则弹出StackOut的栈顶元素;若为空,则逐个弹出StackIn的元素并将其压入StackOut,直到StackIn为空,此时再弹出StackOut的栈顶元素即可。

可以这样理解:队列是FIFO,那么对于双栈实现的队列,第一个进入的元素在StackIn栈底,此时若想输出它,只能先处理它之上的那些元素,处理方法即将这些元素从In转移到Out。最后一个压入Out为原先In的栈底元素,也就是第一个入队的元素。所以此时弹出Out的栈顶元素就是第一个进入的元素了。我们注意到接下来第二,第三,....的元素在Out中也已经排列好了,之后若想出队,就弹出Out中的元素就好了,直到为空,。

在C++标准库提供的stack的基础上实现这样一个队列

template<typename T>
class Stack {
private:std::stack<T> s;
public:void push(const T & e) { s.push(e); }bool empty() { return s.empty(); }//将stl的stack重新封装一下,把pop变为栈弹出的同时也返回值T pop() {T topElement = s.top();s.pop();return topElement;}T top() {return s.top();}
};
template<typename T>
class Queue {
private:Stack<T> stackIn;Stack<T> stackOut;void feedStackOut() {while (!stackIn.empty())stackOut.push(stackIn.pop());}
public:bool empty() { return stackIn.empty() && stackOut.empty(); }void enqueue(T &e) { stackIn.push(e); }T dequeue() {if (stackOut.empty())feedStackOut();//当stackOut为空时,将stackIn中的元素转移到stackOut中去return stackOut.pop();}
};

二、为什么会有用两个栈来实现队列的需要?

2.1 一些函数式语言,如Haskell, ML, Lisp等内置了list类型, 一般是单向链表。这种链表很适合实现一个栈结构,因为我们只需要在表头添加一个节点作为入栈,或是去掉表头作为出栈,这只需要O(1)的时间。但是若用这种内置链表实现队列的话,入队的操作是O(1)的实现,但是出队的操作需要我们遍历到表尾,这需要O(n)的时间。

那么此时用两个栈来实现队列,我们可以将出队的时间经过分摊以后将时间复杂度降为O(1),所以在这些语言中用双栈构建队列可能是一种好的选择。

2.2一些额外的函数功能对于双栈搭建的队列而言更好实现

例如findMax,findMin等查找栈或队列中最小值的函数。

带有这种功能栈的实现思路

#include<vector>
#include<stack>
#define ELEMENT 0
#define MIN 1
#define MAX 2
using namespace std;
template<typename T>
class Stack {
private:std::stack<vector<T>> s;
public:void push(const T & e) {vector<T> v(3, e);//用一个大小为3的向量保存要压入的元素,以及栈中的最大值和最小值if (!empty()) {//更新该向量的最大值最小值v[MIN] = findMin() < v[MIN] ? findMin() : v[MIN];v[MAX] = findMax() > v[MAX] ? findMax() : v[MAX];}s.push(v);}bool empty() { return s.empty(); }//将stl的stack重新封装一下,把pop变为栈弹出的同时也返回值T pop() {vector<T> v = s.top();T topElement = v[ELEMENT];s.pop();return topElement;}T top() {vector<T> v = s.top();T topElement = v[ELEMENT];return topElement;}//由于压入时的比较,我们知道栈顶元素的那个向量保存的是当前栈中所有元素的最大值和最小值T findMax() {vector<T> v = s.top();T max = v[MAX];return max;}T findMin() {vector<T> v = s.top();T min = v[MIN];return min;}
};

由于栈FILO的性质,我们维护最大值最小值时比较容易,因为每个元素所在的那个向量的最大值、最小值都是和之前入栈的元素进行比较得到的。所以当某个元素出栈时,不需要再去检查这个元素之前的那些元素是否需要更新。

但是对于队列而言,其FIFO的性质使得我们很难维护最大值最小值。当我们现在要入队一个元素时,这个元素所在那个向量的最大值最小值,需要跟队伍中已存在的元素进行比较。之后我们如果出队一个元素,因为first in firs out,当前队伍中最早入队的元素出队了,但是我们知道这个排在这个元素后面的那些元素,它们所在的向量的最值均与这个元素比较过,所以这个元素出队后,剩下的每个元素均需要重新与在其前面的元素进行比较,确认最大值、最小值是否发生改变。

所以这个出队时维护最大值、最小值的所耗费的时间是很难接受的。这里需要说明一点的是,对于普通方式实现的队列,不需要像上述stack那样为每个元素维护一个向量。它只需为整个队列,维护两个变量即可。这样出队时,只需进行O(n)的比较即可,否则的话需要进行O(n^2)此比较。

那有没有更好的方式去实现这样一个能够维护最大值最小值的队列呢?有的,就是利用上面实现的那个已具有这些功能的两个栈即可。

思路是这样:当要求队列的最大值/最小值时,我们可以比较两个栈中的最大值/最小值,从而确定队列中的最大值/最小值,拿到两个栈中最大值/最小值是O(1),比较得到队列的最大值/最小值也是O(1),所以我们可以得到一个在时间上复杂度更优的功能队列。不过需要注意处理两个栈中有一个栈为空的特殊情况。

template<typename T>
class Queue {
private:Stack<T> stackIn;Stack<T> stackOut;void feedStackOut() {while (!stackIn.empty())stackOut.push(stackIn.pop());}
public:bool empty() { return stackIn.empty() && stackOut.empty(); }void enqueue(T &e) { stackIn.push(e); }T dequeue() {if (stackOut.empty())feedStackOut();//当stackOut为空时,将stackIn中的元素转移到stackOut中去return stackOut.pop();}T findMax() {if (!empty()) {if (stackIn.empty())return stackOut.findMax();else if (stackOut.empty())return stackIn.findMax();else {T max = stackIn.findMax() > stackOut.findMax() ? stackIn.findMax() : stackOut.findMax();return max;}}}T findMin() {if (!empty()) {if (stackIn.empty())return stackOut.findMin();else if (stackOut.empty())return stackIn.findMin();else {T min = stackIn.findMin() < stackOut.findMin() ?stackIn.findMin() : stackOut.findMin();return min;}}}
};

用两个栈实现一个队列相关推荐

  1. 多态指针访问虚函数不能被继承的类快速排序N皇后问题插入排序堆排序merge归并排序栈上生成对象两个栈实现一个队列...

    多态 /*1. 要想实现覆盖(重写)父类必须声明为virtual,子类可以不声明为virtual.-->FunB()2. 派生类重写基类的虚函数实现多态,要求函数名.参数列表.返回值完全相同.( ...

  2. 【剑指offer】用两个栈实现一个队列

    题目:两个栈实现一个队列. 栈的特点:先进后出,队列的特点是先进先出 思路:stack1放入数据:pop时,先判断stack2是否为空,如果不为空,直接pop,如果空,则push(stack1.pop ...

  3. 剑指offer五:两个栈实现一个队列

    题目描述 用两个栈来实现一个队列,完成队列的Push和Pop操作. 队列中的元素为int类型. package com.jianzhioffer;import java.util.Stack;publ ...

  4. 两个栈实现一个队列,两个队列实现一个栈

    题目:用两个栈实现一个队列,用两个队列实现一个栈. 首先要了解栈和队列这两种数据结构各自的特点,栈是一种后入先出(Last In First Out,LIFO)的数据结构,队列是一种先进先出(Firs ...

  5. python 用两个栈实现一个队列

    | 两个栈实现队列 用两个栈实现一个队列.队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能.(若队列中没有元素 ...

  6. java实现-两个栈实现一个队列和两个队列实现一个栈

    1.两个栈实现一个队列 思路:压入元素直接入stack1,删除元素先判断stack2中是否为空,如果不为空直接弹出:为空则将stack1中的元素取出压入 stack2中再弹出. 代码: import ...

  7. python ——两个队列实现一个栈两个栈实现一个队列

    1.两个队列实现一个栈 进栈:元素入队列A 出栈:判断如果队列A只有一个元素,则直接出队.否则,把队A中的元素出队并入队B,直到队A中只有一个元素,再直接出队.为了下一次继续操作,互换队A和队B. p ...

  8. 两个栈实现一个队列与两个队列实现一个栈

    http://blog.csdn.net/z84616995z/article/details/19204529 两个栈实现一个队列: 原理方法:用一个栈为主栈,一个栈为辅助栈存放临时元素. 入队:将 ...

  9. 【数据结构】(面试题)使用两个栈实现一个队列(详细介绍)

    http://blog.csdn.net/hanjing_1995/article/details/51539578 使用两个栈实现一个队列 思路一: 我们设定s1是入栈的,s2是出栈的. 入队列,直 ...

  10. 两个栈实现一个队列/两个队列实现一个栈

    http://blog.csdn.net/sinat_30472685/article/details/70157227 1两个栈实现一个队列 1.原理分析: 队列的主要操作有两个:入队操作和出队操作 ...

最新文章

  1. 手写js的insertAfter
  2. 全国大学生智能汽车竞赛介绍-2020
  3. ITK:从函数返回对象
  4. 有关EMMC、Nandflash、SSD、HDD的科普类说明
  5. [react] 在使用react过程中什么时候用HOC?
  6. 4岁小女孩给Linux内核贡献提交
  7. java验证只能输入数字和字母_java:为什么我做的验证只能验证数字和字母不重复,不能验证汉字不重复...
  8. RK 3399 切换以太网卡
  9. 域名被封(微信)后的思索
  10. 阿里icon小图标在vue中的教学使用(两种主流方案)
  11. 身份证号码校验(前端,java)
  12. 信奥赛1990:【19CSPS提高组】划分 解题报告(附ac主代码)
  13. 食品生产设备用什么清洗消毒效果比较好?
  14. not in 与not exists区别
  15. redhat linux 系统修复,简述rhel7系统修复
  16. 目标检测—基于Yolov3的目标检测项目实战(学习笔记)
  17. 线段树+KMP-hdu-4125-Moles
  18. 2022-01-17 opencv-3.4.1 zlib.h: No such file or direc
  19. 基于前端Js模块化规范的粗浅应用
  20. 线段树(区间修改)模板题 Luogu 2357 守墓人

热门文章

  1. 「自然语言处理(NLP)论文推送」(中文诗歌生成) 上下文增强Transformer【ACL 微信AI团队 北交】
  2. 罗马java_罗马数字转换工具-java
  3. CrossApp_01
  4. 下载Django中文官方文档
  5. imp 导入dmp文件。
  6. 草图大师SketchUp2019下载与安装教程
  7. xp系统怎样安装传真服务器,Windowsxp系统下设置传真接收的详细步骤
  8. 饥荒联机 显示服务器小麻烦,饥荒联机版新手从入门到精通教程
  9. BLP读书摘录和笔记——make
  10. 图说丨京东《技术重构社会供应链——未来科技趋势白皮书》