王道数据结构C语言实战:线性表与栈的5个高频面试题解析
准备数据结构面试,尤其是针对考研复试或大厂校招,很多人会陷入一个误区:把教材上的概念背得滚瓜烂熟,但一遇到需要动手写代码的题目就卡壳。我当年备考时也踩过这个坑,直到在面试现场被要求在白板上实现一个“两栈共享空间”时,才发现自己理解的“掌握”和实际能写出来的“掌握”完全是两码事。这篇文章不会重复那些你已经看过无数遍的理论定义,而是聚焦于五个在面试中反复出现、又容易写错的线性表和栈的C语言实战题目。我会带你一步步拆解思路,给出可以直接运行的代码,并重点分析那些教科书上不会明说、但实际编码时一定会遇到的“坑”。
1. 两栈共享空间:如何优雅地“一石二鸟”
两栈共享空间这个题目,几乎成了数据结构面试的“保留节目”。它考察的不仅仅是你对栈的理解,更是对顺序存储结构内存管理的敏感度。核心思想很简单:一个数组,两个栈,一个从数组头部开始增长(栈1),一个从数组尾部开始增长(栈2),向中间靠拢。听起来容易,但实现时细节决定成败。
首先,我们需要一个结构体来管理这个共享空间。很多同学在这里会犯第一个错误:栈顶指针的初始化。
#define MAXSIZE 100 // 共享栈的最大容量
typedef struct {
int data[MAXSIZE];
int top1; // 栈1的栈顶指针
int top2; // 栈2的栈顶指针
} DoubleStack;
初始化时,top1 应该设为 -1,表示栈1为空;top2 应该设为 MAXSIZE,表示栈2为空。这里 top2 指向的是栈顶元素的下一个位置,这是一种常见的实现方式,但你必须非常清楚它的含义,因为这会直接影响判满和判空的条件。
栈满的条件是面试官最喜欢追问的地方。不是 top1 == top2,而是 top1 + 1 == top2。为什么?因为 top1 指向的是栈1的栈顶元素,top2 指向的是栈2的栈顶元素的下一个位置。当两个栈的栈顶元素相邻时,数组就满了。下面是一个完整的初始化及入栈函数实现:
// 初始化双栈
void InitDoubleStack(DoubleStack *S) {
S->top1 = -1;
S->top2 = MAXSIZE;
}
// 向栈1压入元素
int Push1(DoubleStack *S, int e) {
if (S->top1 + 1 == S->top2) { // 栈满判断
printf("栈满,无法压入元素 %d\n", e);
return 0; // 失败
}
S->data[++(S->top1)] = e; // 先移动指针,再赋值
return 1; // 成功
}
// 向栈2压入元素
int Push2(DoubleStack *S, int e) {
if (S->top2 - 1 == S->top1) { // 另一种等价的栈满判断
printf("栈满,无法压入元素 %d\n", e);
return 0;
}
S->data[--(S->top2)] = e; // 先移动指针,再赋值
return 1;
}
注意:在
Push2中,我们使用--(S->top2)是因为栈2是反向增长的。top2初始为MAXSIZE,减1后指向数组最后一个有效位置(MAXSIZE-1),这正是栈2的栈底。随着元素压入,top2会逐渐减小。
出栈操作相对简单,但务必记得先判空。栈1空的条件是 top1 == -1,栈2空的条件是 top2 == MAXSIZE。我见过不少同学在面试紧张时,把判空条件写反。
这种结构的优缺点非常明显:
- 优点:在需要两个相同类型的栈,且此消彼长(一个栈增长时另一个栈在减少)的场景下,能有效利用存储空间,避免一个栈满另一个栈还空着的情况。
- 缺点:必须事先预估两个栈可能的最大容量总和。如果两个栈同时快速增长,仍然会很快栈满。并且,这种结构只适用于两个栈,扩展性差。
2. 循环队列:告别“假溢出”的经典陷阱
顺序队列的“假溢出”问题——即队列头部有空闲位置,但尾部指针已到数组末尾,导致无法再入队——是面试中的经典考点。循环队列是解决这个问题的标准方案,但它的实现细节堪称“陷阱重重”,尤其是队空和队满的判定。


1190

被折叠的 条评论
为什么被折叠?



