考408的同学应该对“数据结构选择题第1题”都有印象——它往往是整套卷子里最容易拿分、也最容易因疏忽失分的一道题。2010年这道关于栈基础操作的真题,表面上是问“栈的容量至少是多少”,实际上考的是你有没有真正理解栈的后进先出特性,能不能把一个元素从“入栈”到“出栈”再到“入队、出队”的完整过程在脑中动态推演出来。
这道题的经典之处在于,它不要求你写代码,不涉及复杂的算法设计,纯粹考察“栈最基础的操作逻辑”。但恰恰是这种基础题,每年都能让一部分人在“容量至少为几”这个问题上翻车。接下来我带你完整拆一遍这道题,把题干、考点、推演过程、常见错误全部说透,最后再顺着它延伸一下408中栈的常考题型。不管你是一战刚开始复习数据结构的新手,还是二战需要查漏补缺的选手,这篇文章都能给你一些实实在在的参考。
1. 题目还原与考点定位
1.1 原题题干与选项
2010年全国硕士研究生入学统一考试计算机学科专业基础综合(408)数据结构部分第1题,题目大致是这样描述的:
设栈S和队列Q的初始状态均为空,元素a,b,c,d,e,f,g依次进入栈S。若每个元素出栈后立即进入队列Q,且7个元素出队的顺序是b,d,c,f,e,a,g,则栈S的容量至少是( )。
A. 2 B. 3 C. 4 D. 5
这道题属于“栈和队列综合应用”的基础题型,难度系数不算高,但它把栈的LIFO(后进先出)和队列的FIFO(先进先出)捆绑在一起考察,还涉及“最小容量”的推断。很多人看到“容量至少是多少”就发懵,本质上是没有建立“元素流动过程”的直观画面。
1.2 这道题为什么值得反复做
从应试角度说,2010年是408统考的第二个年份,命题风格已经趋于稳定。这道题虽然放在第1题,但它综合了两个线性结构——栈和队列,还包含了一个最值问题(最小容量)。这为我们提供了一种很好的复习思路:不要孤立地记“栈的特点是什么,队列的特点是什么”,而是要学会分析它们协同工作时的状态变化。
从知识体系角度说,栈的入栈、出栈、判断栈是否为空、获取栈内元素数量,这些操作就是后续学习递归、函数调用、表达式求值、括号匹配、深度优先搜索等内容的根基。把这道题吃透,相当于把“栈的动态变化过程”这一核心认知补齐了。
2. 考点拆解:题干里的每个条件都不是废话
2.1 栈与队列的核心特性复习
要解这道题,首先要把两个结构的特性刻在脑子里:
- 栈:只在栈顶进行插入和删除操作,后进先出。就像往箱子里叠衣服,后放进去的衬衫必须先拿出来。
- 队列:在一端(队尾)插入,在另一端(队头)删除,先进先出。就像排队打饭,先来的先打。
题目描述“元素a,b,c,d,e,f,g依次进入栈S”,需要特别注意“依次进入”不等于“连续全部进入”。在实际操作中,完全可以在某个元素入栈后、下一个元素入栈前,先执行若干次出栈操作。这一点在后面的推演中会体现得非常明显。
2.2 每个元素“出栈后立即进入队列Q”意味着什么
题目说“每个元素出栈后立即进入队列Q,且7个元素出队的顺序是b,d,c,f,e,a,g”。这里有一个非常关键的等价关系:
因为队列是先进先出,所以“出队顺序”与“入队顺序”完全一致;而入队顺序就是出栈顺序。
换言之,题目给出的出队顺序,直接等价于出栈顺序。也就是说,这7个元素依次出栈的序列必须是:
b, d, c, f, e, a, g
这样一来,问题就转化为一个更纯粹的形式:输入序列为a,b,c,d,e,f,g(按顺序入栈),要求通过合法的入栈、出栈操作,得到输出序列b,d,c,f,e,a,g。求栈的最小容量。
2.3 “容量至少是多少”背后的数学含义
栈的容量,指的是栈中最多能同时容纳的元素个数。在整个操作过程中,栈内元素数量是动态变化的:入栈时加1,出栈时减1。所谓“至少需要多少容量”,就是问这个动态过程中栈内元素数量的峰值是多少。
这就像你在电梯里不断有人进、有人出,电梯容量必须大于等于同一时刻电梯里的最大人数。理解了这一点,后面不管怎么模拟,目标都很明确:找峰值。
3. 手把手推演:从a到g的完整状态变化
3.1 推演前的两个认知准备
第一,入栈顺序固定为a,b,c,d,e,f,g,也就是说a最早入栈,g最晚入栈。
第二,出栈顺序必须完全等于b,d,c,f,e,a,g。那么第一个出栈的元素必须是b。b现在是序列中的第二个元素,为了让它第一个出栈,我们需要:
- a先入栈;
- b再入栈;
- 然后b出栈。
这里就体现出“依次进入”不等于“全部进入”。如果我们让a到g全部入栈之后才允许出栈,那第一个出栈的必然是g,与题目要求矛盾。所以必须采用“边入栈边出栈”的策略。
一个更高层的认知是:这道题本质上是“给定入栈序列和出栈序列,判断合法性并求最小栈空间”的经典问题。合法性靠模拟验证,最小空间靠记录栈内峰值得到。
3.2 逐步模拟与状态记录
下面用一个完整的过程表格来记录每一步操作。表格中“栈内元素(自底向上)”这一列,能直观反映栈里同时存在几个元素。
| 步骤 | 操作 | 栈内元素(自底向上) | 队列Q中的元素(从队头到队尾) | 栈内元素数量 | 当前最大容量需求 |
|---|---|---|---|---|---|
| 1 | a入栈 | a | (空) | 1 | 1 |
| 2 | b入栈 | a, b | (空) | 2 | 2 |
| 3 | b出栈并入队 | a | b | 1 | 2 |
| 4 | c入栈 | a, c | b | 2 | 2 |
| 5 | d入栈 | a, c, d | b | 3 | 3 |
| 6 | d出栈并入队 | a, c | b, d | 2 | 3 |
| 7 | c出栈并入队 | a | b, d, c | 1 | 3 |
| 8 | e入栈 | a, e | b, d, c | 2 | 3 |
| 9 | f入栈 | a, e, f | b, d, c | 3 | 3 |
| 10 | f出栈并入队 | a, e | b, d, c, f | 2 | 3 |
| 11 | e出栈并入队 | a | b, d, c, f, e | 1 | 3 |
| 12 | a出栈并入队 | (空) | b, d, c, f, e, a | 0 | 3 |
| 13 | g入栈 | g | b, d, c, f, e, a | 1 | 3 |
| 14 | g出栈并入队 | (空) | b, d, c, f, e, a, g | 0 | 3 |
从表格第5步可以看出,当a, c, d三个元素同时在栈中时,栈内元素数量达到最大值3。所以栈的容量至少是3,答案选B。
注意第8步和第9步:e、f先后入栈时,栈内是a和e(两个元素),f入栈后变成a, e, f(三个元素),但没有超过3。第13步g入栈时,因为a已经出栈,栈里只有g一个元素。整个过程中,没有任何时刻需要容纳4个元素。
3.3 另一种快速验证思路
如果你不想每一步都画表格,可以在草稿纸上用“箭头法”推演。具体做法是:
把输入序列写在左边,出栈序列写在右边。然后按出栈顺序逐个匹配:
- 当前要出b,那么从输入序列中依次读入a、b,入栈后立即出b。
- 当前要出d,那么读入c、d,入栈后立即出d。
- 当前要出c,此时c正好在栈顶,直接出c。
- 当前要出f,那么读入e、f,入栈后立即出f。
- 当前要出e,此时e在栈顶,直接出e。
- 当前要出a,此时a在栈底但也是栈中唯一元素,直接出a。
- 当前要出g,读入g,入栈后立即出g。
每读入一个元素,就记录当前栈内元素个数,最后取最大值。这种方法本质上是“贪心”地匹配出栈序列,效率高,也不容易漏掉状态。对于这种选择题,30秒内就能得出答案。
我还想多说一句:做题时不要怕“在纸上画格子”。画格子是数据结构题目最有效的辅助手段之一,尤其是遇到需要模拟过程的题。把栈画成一个上下开口的容器,队列画成一条水平通道,每操作一步就更新图形,答案就会自己浮出来。
4. 做题中的高频错误与避坑经验
4.1 错误一:把“依次进栈”理解成“全部进栈再出栈”
这是我见过最多的错误。很多同学一看到“元素a,b,c,d,e,f,g依次进入栈S”,就默认是七次入栈操作全部执行完之后,才开始出栈操作。这种理解下,第一个出栈的必定是g,而题目给的是b,直接矛盾,于是这道题根本做不下去。
实际上,“依次进入”只是规定入栈操作的顺序,并没有说入栈和出栈不能交替进行。如果把栈想象成手枪的弹匣,压一颗子弹发射一颗子弹,你就会明白“边入栈边出栈”是多么自然的场景。408历年真题中,凡是涉及出入栈序列的题目,默认都是允许交替操作的。
4.2 错误二:只盯着某一时刻的栈内元素数量
有些同学能顺利模拟出前几步,但在计算容量时犯了迷糊:看到第3步b出栈后栈里只有a,就以为容量是1;看到第6步d出栈后栈里只有a和c,就以为容量是2。这些都是没有抓住“峰值”的关键。
容量的要求取决于整个过程中“同时存在的最大元素数量”,不是最后阶段的数量,也不是某个中间阶段的数量。就好比你租房,一年中只有一个月同时住了3个人,那也得找能住3个人的房子。做题时建议每操作一步就用铅笔在草稿纸角落记下当前栈内元素数量,最后统一比较。
4.3 错误三:混淆队列的出队顺序和入队顺序
本题有一个隐蔽陷阱:题目给出的是“出队顺序”,而不是“出栈顺序”。如果对队列的FIFO特性不敏感,可能会误以为出队顺序和入栈顺序有关联,导致思路混乱。
实际上,由于每个元素出栈后“立即”进入队列,而入队顺序一定等于出栈顺序,队列先进先出又保证出队顺序一定等于入队顺序,所以三个顺序完全一致:出栈顺序 = 入队顺序 = 出队顺序。这个等价关系一旦没想清楚,后面整个推演都会跑偏。建议在题目旁边先写下这个等价关系,再开始模拟。
4.4 面试和考试之外的现实意义
这道题不只是应试技巧。在实际工程里,栈的容量问题对应着函数调用栈的深度问题。每一次函数调用都会在栈上分配栈帧,如果递归过深或者局部变量过大,就会导致栈溢出。理解了“找栈内峰值”的思路,你就理解了为什么某些递归算法在大规模数据下会崩溃,为什么需要改用循环或显式栈。这也是408考试不只是“背答案”的重要原因——它其实在训练你建立计算思维。
5. 从这道题延伸:栈在408中的常考题型与备考建议
5.1 合法出栈序列的通用判断方法
2010年的第1题是“给定出栈序列,求最小栈容量”。还有一个更常见的变体是:“已知入栈序列为1,2,3,...,n,问下列哪个出栈序列是合法的?”
这类题有一个经典结论:对于出栈序列中的任意元素x,排在x后面且比x小的所有元素,必须按降序排列。举个例子,若入栈顺序是1,2,3,4,5,出栈序列是4,5,3,2,1,我们来验证:对4来说,后面比4小的是3,2,1,它们是降序排列,合法;对5来说,后面比5小的是3,2,1,也是降序排列,合法。所以整个序列合法。
为什么?因为比x先入栈的元素,如果还没出栈,在x出栈后它们只能按从栈顶到栈底的顺序依次出栈,即从大到小(后进先出)的顺序。这个结论在判断“合法出栈序列”时非常好用,比逐项模拟快得多。
5.2 栈与队列结合的同类历年考题
2010年这道题并不是孤例。408及各大高校自主命题中,栈与队列联动的题目反复出现。常见的出题方向有:
- 两个栈共享一个数组空间:考察共享栈的栈底设置和栈满判断条件。
- 用队列模拟栈,或反过来用栈模拟队列:比如用两个栈实现队列的先进先出,经典题目是LeetCode 232;用两个队列实现栈,是LeetCode 225。
- 判断一段操作序列能否用某个容量受限的栈完成:本质上与2010年这道题一致,只是加了容量限制条件。
- 循环队列中元素数量的计算:给定队头指针、队尾指针和最大容量,求队列长度。
这些题目都建立在对“栈的动态过程”和“队列的先进先出”的深刻理解上。建议你把2010年这道题作为母题,把上面提到的变体都练习一遍,形成自己的“栈队列综合题解题模板”。
5.3 栈基础操作的代码级掌握
虽然这道真题是选择题,不要求写代码,但408的大题部分经常出现栈的应用,比如中缀表达式转后缀表达式、括号匹配、递归函数的非递归实现等。这些题目需要你亲手写出栈的基本操作。我建议至少能手写以下代码模板:
// 顺序栈的常用操作模板 #define MaxSize 100 typedef struct { int data[MaxSize]; int top; // 栈顶指针,指向栈顶元素位置 } SqStack; // 初始化 void InitStack(SqStack &S) { S.top = -1; } // 判空 bool StackEmpty(SqStack S) { return S.top == -1; } // 入栈 bool Push(SqStack &S, int x) { if (S.top == MaxSize - 1) { return false; // 栈满 } S.data[++S.top] = x; return true; } // 出栈 bool Pop(SqStack &S, int &x) { if (S.top == -1) { return false; // 栈空 } x = S.data[S.top--]; return true; } // 读栈顶元素 bool GetTop(SqStack S, int &x) { if (S.top == -1) { return false; } x = S.data[S.top]; return true; }这套模板覆盖了顺序栈最基本的功能。代码中需要注意的是:入栈是先移动top指针再赋值,出栈是先取值再移动top指针,顺序搞反就会导致数据错位。如果你是C语言考生,建议用C实现一遍;如果是C++考生,除了手写栈之外,还要熟悉STL中stack的用法,比如push、pop、top、empty、size这些接口。
5.4 给备考者的刷题建议
从我个人的备考经验来看,数据结构的复习不能只“看书”,要“动手画、动手写、动手算”。对于栈和队列这一章,我建议这样做:
第一,把教材上关于栈的存储结构、操作特点、应用场景的基础概念先梳理清楚。王道或天勤的辅导书在这一章都有不错的知识框架,但不要只看不练。
第二,至少独立完成10道以上的出入栈序列判断题。可以是408真题,也可以从王道书上的习题摘选。每道题都用“模拟法”做一遍,再用“降序结论法”或“容量峰值法”验证一遍,两种方法交叉检验,加深理解。
第三,动手写代码。不要觉得选择题不需要写代码。栈的初始化、判空、入栈、出栈、读栈顶是后续学习所有数据结构的基础,代码写得越熟练,考场上越有信心。特别是报考自命题院校的同学,代码题几乎是必考项。
第四,把错题整理成专题。比如我当年就专门整理了一个“栈与队列错题本”,把凡是涉及到容量推断、合法序列判断、栈溢出场景的题目放在一起,考前一个月集中复盘,效果很好。
后记
说句实在话,408这道栈的基础操作题并不难,但它在考研圈里能一直被人提起,是因为它把“基础”考出了“层次”。从表面看,它是在问栈的容量;往深一层看,它是在考你对栈的动态行为是否敏感;再往深一层看,它是在训练一种非常重要的工程直觉——任何有容量限制的结构,都必须关注峰值负载。
我当年做这道题的时候,一开始也犯了“全部入栈再出栈”的错误,在草稿纸上画了好几遍才反应过来。后来我把这个教训总结成一句话:栈的题目,动笔模拟永远比空想靠谱,记录峰值永远比猜答案可靠。希望这篇文章也能帮你把这道经典题真正吃透。