【刷题-PTA】堆栈模拟队列(代码+动态图解)
题目
题目描述 :
设已知有两个堆栈S1和S2,请用这两个堆栈模拟出一个队列Q。
所谓用堆栈模拟队列,实际上就是通过调用堆栈的下列操作函数:
int IsFull(Stack S)
:判断堆栈S
是否已满,返回1或0;int IsEmpty (Stack S )
:判断堆栈S
是否为空,返回1或0;void Push(Stack S, ElementType item )
:将元素item
压入堆栈S
;ElementType Pop(Stack S )
:删除并返回S
的栈顶元素。实现队列的操作,即入队
void AddQ(ElementType item)
和出队ElementType DeleteQ()
。输入格式:
输入首先给出两个正整数
N1
和N2
,表示堆栈S1
和S2
的最大容量。随后给出一系列的队列操作:A item
表示将item
入列(这里假设item
为整型数字);D
表示出队操作;T
表示输入结束。输出格式:
对输入中的每个
D
操作,输出相应出队的数字,或者错误信息ERROR:Empty
。如果入队操作无法执行,也需要输出ERROR:Full
。每个输出占1行。输入样例:
3 2 A 1 A 2 A 3 A 4 A 5 D A 6 D A 7 D A 8 D D D D T
输出样例:
ERROR:Full 1 ERROR:Full 2 3 4 7 8 ERROR:Empty
分析题目
- 题目要求我们通过 栈数据 结构来模拟实现 队列 , 我们首先可以从两种数据结构的特点进行分析,然后找到解题的切入点.
- 栈数据结构的特点是先进后出(FILO) , 队列数据结构的特点是先进先出(FIFO) , 现在要实现 用栈模拟队列的 增删(CR) .
- 如果我们使用一个栈来实现队列是无法完成要求的,因为二者的结构特点完全不同,但是如果我们把栈的数量增加,也就是使用两个栈来模拟,一个负责出队的栈
s1
,另一个负责入队的栈s2
,两个栈通过互相倒数据 ,来模拟出队和入队的操作. - 具体实现的时候,我们要时刻保证 负责出队的栈
s1
不为空的情况下,栈帧位置的元素(即栈顶元素)始终是最先插入的元素(也就是可以出队的元素) , 负责入队的栈s2
不为空的情况下, 栈帧位置的元素(即栈顶元素始)终是最后插入的元素(也就是刚入队的元素) --> **简单总结就是 : 负责出队的 s1 栈顶只能存放队头的元素, 负责入队的 s2 栈顶只能存放队尾的元素 , 具体怎么让这两个栈始终维持这样的状态就需要两个栈相辅相成 **
区分两栈
- 我们需要考虑两种情况
- 如果两个栈的容量相同,那么哪个栈负责出队,哪个栈负责入队,就不用区分了,可以任选一个作为出队,剩下的就作为入队。
- 如果两个栈的容量不相同, 那么只能容量小的栈负责入队,容量大的负责出队。
问 :为什么当两个栈的容量不相同时,必须让容量小的负责入队,容量大的负责出队呢 ?
答 : 如果一个栈的容量小于另一个栈,将容量小的栈负责入队,容量大的栈负责出队,在特定情况下可能会导致无法完全倒出元素的问题。这是因为当负责入队的栈装满后,向负责出队的栈(必须为空)中倒元素时,负责出队的栈由于容量更小则无法容纳负责入队的栈中所有的元素,从而导致负责出队的栈的栈顶元素一定不是应该出队的元素(两栈中现存的所有元素中最先进来). 所以通常情况下,该元素依旧在负责入队的栈中,那么当你进行出队操作的时候,就会出队错误。所以通常情况下,队列的实现要求队头和队尾的栈具有相同的容量,以确保操作的一致性。但是如果题目指定了两个栈的容量不同,那么此时我们就需要特别处理以避免出现上述问题。
解题思路
封装了三个方法,键盘输入,如果输入A 表示 入队 Push , D 表示 出队 Pop ,T 表示 操作结束.
那么我们就应该写一个输入的循环,如果输入 A 就进行入队操作, 输入D就进行出队操作, 输入T就结束操作
- 入队操作:
- s2 没有满
只要s2 没有满就往s2中添加元素 - s2满了
如果s2满了且s1为空,就把s2中所有的元素全部倒给s1
如果s2满了且s1不为空,那么插入失败 ,打印ERROR:Full
- 出队操作:
- s1不为空
只要s1不为空就出队 - s1为空
s1为空就出队失败,打印ERROR:Empty
- 结束操作 : 直接 break
伪代码
我们在真正上手去写具体的代码之前可以事先按照解题的流程写出伪代码,写完之后,我们再按照伪代码走一遍,看看是否符合解题的逻辑,这个时候就相当于是在把正确答案的框架先体现测试出来.
动图演示
输入样例: 3 2 A 1 A 2 A 3 A 4 A 5 D A 6 D A 7 D A 8 D D D D T
(动态演示图太大,不能传过来,如果需要动动态演示图,可以评论区冒泡,我发你.)
代码
import java.util.Scanner; import java.util.Stack; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 辅助栈1, 用来入队 Stack<Integer> s1 = new Stack<>(); // 辅助栈2, 用来出队 Stack<Integer> s2 = new Stack<>(); // 栈1的容量 int N1 = scanner.nextInt(); // 栈2的容量 int r2 = scanner.nextInt(); while (scanner.hasNextLine()) { String opr = scanner.next(); if (opr.equals("A")) { int val = scanner.nextInt(); if (s2.size() < r2) { Push(s2, val); } else{ if (s1.isEmpty()) { // s2满了,但s1是空的,那么就把s2的全部都移动到s1中去 while (!s2.empty()) { Push(s1, s2.pop()); } Push(s2, val); } else { System.out.println("ERROR:Full"); } } } else if (opr.equals("D")) { if (!s1.isEmpty()) { int front = Pop(s1); System.out.println(front); } else if (!s2.isEmpty()) { // 如果s1为空但s2不为空,可以从s2中pop一个元素 int front = Pop(s2); System.out.println(front); } else { System.out.println("ERROR:Empty"); } } else if (opr.equals("T")) { break; } } } /** * 判断堆栈S是否已满,返回1或0 * @param s 栈 * @return 返回 1 表示满,返回 0 表示没有满 */ public static int IsFull(Stack<Integer> s,int r){ if(s.size() >= r){ return 1; } return 0; } /** * 判断堆栈S是否为空,返回1或0; * @param s 栈 * @return 返回 1 表示空,返回 0 表示不为空 */ public static int IsEmpty (Stack<Integer> s ){ if(s.empty()){ return 1; } return 0; } /** * 将元素item压入堆栈S; * @param s 栈 * @param value 准备压入的数据 */ public static void Push(Stack<Integer> s, int value ){ s.push(value); } /** * 删除并返回S的栈顶元素。 * @param s 栈 * @return 返回的栈顶元素 */ public static int Pop(Stack<Integer> s ){ return s.pop(); } }
测试