栈的顺序存储与链式存储
这一节的题只考一件事:top 指向哪里、往哪个方向长,入栈出栈的语句就跟着变。 教材注意框原话:「栈顶、队头与队尾的指针的定义是不唯一的,做题时务必仔细审题和思考。」判空判满条件收在 速查:栈与队列的判空判满,本页讲怎么从约定推出语句。
机制
3.1.2 顺序栈
利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针 top 指示当前栈顶元素的位置。
#define MaxSize 50
typedef struct{
ElemType data[MaxSize];
int top; // 栈顶指针
}SqStack;教材的默认约定是 top 指向栈顶元素,初始 S.top=-1:
| 项 | 写法 |
|---|---|
| 栈顶元素 | S.data[S.top] |
| 入栈 | 栈不满时,栈顶指针先加 1,再送值到栈顶:S.data[++S.top]=x; |
| 出栈 | 栈非空时,先取栈顶元素,再将栈顶指针减 1:x=S.data[S.top--]; |
| 栈空 / 栈满 / 栈长 | S.top==-1 / S.top==MaxSize-1 / S.top+1 |
另一种常见约定是 top 指向栈顶元素的下一个位置,初始 S.top=0:入栈先送值再加 1,出栈先减 1 再取值;栈空 S.top==0,栈满 S.top==MaxSize。
出栈只移动指针,不清除数据。 教材图 3.2(d):A~E 依次入栈后 E、D、C 相继出栈,C、D、E 可能仍在原先的单元存储着,但 top 已经指向新的栈顶,它们已不在栈中(2009 命题追踪「出/入栈操作的模拟」)。
顺序栈的入栈受数组上界约束,最大使用空间估计不足时可能发生栈上溢。
共享栈
两个顺序栈共享一个一维数组空间,两个栈底分别设在共享空间的两端,两个栈顶向中间延伸。
| 项 | 0 号栈(左) | 1 号栈(右) |
|---|---|---|
| 栈空 | top0==-1 | top1==MaxSize |
| 入栈 | top0 先加 1 再赋值 | top1 先减 1 再赋值 |
| 出栈 | 先取值再减 1 | 先取值再加 1 |
栈满:两个栈顶指针相邻,top1-top0==1。 共享栈为了更有效地利用存储空间,两个栈的空间相互调节,只有整个存储空间被占满时才发生上溢。存取数据的时间复杂度仍为
3.1.3 链栈
采用链式存储的栈。优点是便于多个栈共享存储空间和提高效率,且不存在栈满上溢的情况。
通常用单链表实现,所有操作都在单链表的表头进行。教材规定链栈没有头结点,Lhead 指向栈顶元素。
操作(不带头结点,top 指向栈顶结点) | 写法 |
|---|---|
入栈 x 结点 | x->next=top; top=x; |
出栈并存入 x | x=top->data; top=top->next; |
带头结点和不带头结点的链栈,具体实现会有所不同。
手算模板
入栈、出栈语句由两个约定决定:top 指向栈顶元素还是其下一个位置;栈向高地址还是低地址增长。
top 指向 | 增长方向 | 入栈 | 出栈 |
|---|---|---|---|
| 栈顶元素 | 高地址 | data[++top]=x | x=data[top--] |
| 栈顶元素的下一个位置 | 高地址 | data[top++]=x | x=data[--top] |
| 栈顶元素 | 低地址 | data[--top]=x | x=data[top++] |
| 栈顶元素的下一个位置 | 低地址 | data[top--]=x | x=data[++top] |
判断依据:top 指向栈顶元素时,入栈要先移到空位再存;top 指向下一个位置时,它本身就是空位,先存再移。出栈正好反过来。
从初始值反推约定:数组下标 top=1 说明指向下一个位置(向高长),top=n+1 说明指向栈顶元素(向低长)。王道 3.1.4 第 4、5、6 题分别对应表中第 1、2、3 行,详见 3.1.1 的错题复盘。
边界
| 说法 | 判断 | 说明 |
|---|---|---|
| 「栈和队列的存储结构相同」 | ❌ | 它们具有相同的逻辑结构(都是线性结构),只是对数据的运算不同 |
| 「栈是顺序存储的线性结构」 | ❌ | 栈可以顺序存储也可以链式存储。准确说法是限制存取点的线性结构 |
| 「删除栈底元素是栈的基本操作」 | ❌ | 不属于基本运算,但可以通过调用基本运算求得 |
| 「出栈后元素就从存储单元里被清除了」 | ❌ | 只是 top 移动了,数据可能仍在原单元;逻辑上已不在栈中 |
| 「链栈的优势是插入、删除更容易实现」 | ❌ | 优势是通常不会出现栈满,可以动态分配存储空间 |
| 「共享栈能减少存取时间」 | ❌ | 存取仍是 |
| 「共享栈降低下溢的可能」 | ❌ | 降低的是上溢:存储器满了还往里写。下溢是存储器空了还往外读 |
「共享栈栈满是 top1==top2」 | ❌ | top1=-1、top2=n 起步时,栈满是 top2-top1==1 |
| 「任何单链表都适合做链栈」 | ❌ | 只有表头指针、没有表尾指针的单向循环链表最不适合:在表头插入删除后要更新尾结点的 next,找尾结点需 |
错题复盘:栈顶指针的地址计算(3.1.4 第 7 题)
空栈的栈顶指针为 1000H,栈向高地址增长,每个元素占一个存储单元。执行
push, push, pop, push, pop, push, pop, push后,栈顶指针为 1002H。 每次push加 1、pop减 1,依次为 1001H、1002H、1001H、1002H、1001H、1002H、1001H、1002H。只看最终的入栈次数减出栈次数(5−3=2)即可。
错题复盘:
GetTop不出栈(3.1.4 第 12 题)
InitStack(st); Push(st,a); Push(st,b); Pop(st,x); GetTop(st,x);之后x的值为 a。Pop让x=b并弹出 b;GetTop读出当前栈顶 a 覆盖x,但 a 仍留在栈中。
考点
- 两种
top约定 × 两种增长方向,对应四组入栈出栈语句。 - 出栈只移指针,不清数据(2009 命题追踪)。
- 共享栈:栈满
top1-top0==1;好处是节省空间、降低上溢。 - 链栈:不带头结点,操作都在表头;优势是不会栈满。
链接
- 🏠 返回总览:数据结构第 1~4 章总览
- ⬅️ 上一节:3.1.1 出栈序列:合法性判定与卡特兰数计数
- ➡️ 下一节:3.2.1~3.2.3 队列与循环队列
- 🔗 判空判满总表:速查:栈与队列的判空判满
- 🔗 顺序栈的代码:板子:顺序栈与循环队列
- 🔗 「只有头指针的单向循环链表」的同类推理:2.3.3~2.3.6 双链表、循环链表与静态链表
- 📖 名词库:第 1~4 章名词库