3.1 栈的定义

栈 (Stack)
只允许在一端进行插入或删除操作的线性表。允许操作的一端称为栈顶 (top),另一端称为栈底 (bottom);不含任何元素的栈称为空栈。

栈的特性是 后进先出 (LIFO, Last In First Out):最后入栈的元素最先出栈。

3.2 栈的基本操作

进出栈序列与 Catalan 数

对入栈序列 1,2,…,n,其合法的出栈序列数即 n 个元素的不同出栈顺序数,等于 第 n 个卡特兰数

Cₙ = C(2n, n) / (n+1) = (2n)! / [(n+1)! · n!]

当 n=3 时,C₃=5,1,2,3 的合法出栈序列共 5 种:1 2 3、1 3 2、2 1 3、2 3 1、3 2 1。注意 3 1 2 非法

判断合法出栈序列:模拟入栈出栈过程,若任意时刻出栈元素大于当前栈顶但小于未入栈的最小元素,则非法。最稳妥方法是直接模拟。

3.3 栈的应用

1. 括号匹配

扫描表达式,遇左括号入栈,遇右括号则与栈顶左括号匹配并出栈;若栈空或不匹配则出错;扫描结束栈空即匹配成功。

2. 递归调用

函数调用时,系统用递归工作栈保存每层的实参、局部变量和返回地址。递归深度过大会导致栈溢出。递归 → 迭代转换常借助显式栈。

3. 表达式求值

借助运算符栈操作数栈实现。常见三种表达式:

3.4 队列的定义

队列 (Queue)
只允许在一端(队尾 rear)插入、另一端(队头 front)删除的线性表。特性为 先进先出 (FIFO, First In First Out)

基本操作:InitQueueQueueEmptyEnQueue(入队)、DeQueue(出队)、GetHead(取队头)。

3.5 循环队列

顺序队列会发生"假溢出"(rear 到达数组末尾但前端有空位)。将数组视为首尾相接的环,即循环队列。设数组大小 MaxSize:

为何牺牲一个单元:若不牺牲,则"队空"与"队满"判别条件都是 front==rear,无法区分。牺牲一格后二者得以区分,队列最多容纳 MaxSize−1 个元素。

循环队列操作动画(入队 / 出队 / 队满 / 队空)

交互式
速度1x

3.6 双端队列

允许两端都可以进行入队和出队操作的队列。进一步受限可派生出:

3.7 多维数组的存储

高级语言中的多维数组在内存中必须映射为一维线性地址,主要有两种映射方式。

1. 按行优先(行主序 Row-major)

先按行号从小到大排列,同一行内按列号从小到大排列。C/C++、Pascal 等语言采用此方式。

2. 按列优先(列主序 Column-major)

先按列号从小到大排列,同一列内按行号从小到大排列。Fortran、MATLAB 等语言采用此方式。

n 维数组的一般化地址映射公式

设 n 维数组声明为 A[d₁][d₂]…[dₙ],元素 A[i₁][i₂]…[iₙ] 的存储位置:

本质上,按行优先是从最左维向最右维"展开",按列优先则是从最右维向最左维"展开"。可以通过计算前面完整"块"的个数来推导任一元素的偏移量。

按行优先 vs 按列优先示例

以二维数组 A[3][4] 为例,设 LOC(A[0][0])=100,L=4 字节:

元素按行优先地址按列优先地址
A[0][0]100 + 0×4 = 100100 + 0×4 = 100
A[0][1]100 + 1×4 = 104100 + 3×4 = 112
A[1][0]100 + 4×4 = 116100 + 1×4 = 104
A[2][3]100 + 11×4 = 144100 + 11×4 = 144
关键结论:按行优先时,同一行的元素地址连续;按列优先时,同一列的元素地址连续。连续访问同一维度的相邻元素时,应匹配存储方式以获得最佳缓存局部性。

3.8 特殊矩阵的压缩存储

对含大量重复或对称元素的矩阵,可只存部分元素以节省空间,并建立映射关系 (i,j) → k(1 下标)。

1. 对称矩阵

满足 a[i][j]=a[j][i]。只存下三角(含主对角线),按行优先存入一维数组 B,大小 n(n+1)/2。

if i >= j: k = i(i-1)/2 + j-1
else:       k = j(j-1)/2 + i-1   // 对称映射到下三角

2. 三角矩阵

下三角矩阵:下三角存元素,上三角全为常数 c。存下三角 n(n+1)/2 个 + 1 个常数,共 n(n+1)/2+1。上三角矩阵类似。

3. 三对角矩阵

仅主对角线及其上下相邻两条对角线有非零元,共 3n−2 个元素。按行优先映射:

k = 2i + j - 3   // |i-j| <= 1 时,1 下标

3.9 栈入栈出过程可视化

对入栈序列 1 2 3 4,对比两种典型操作:① 入栈即出栈(出栈序 = 入栈序);② 全部入栈后出栈(出栈序反转)。这正是 LIFO 特性的体现。

栈 push / pop 过程

交互式动画
速度 1x
步骤 0/0

3.10 括号匹配动画演示

栈的经典应用:扫描表达式,遇左括号入栈、遇右括号匹配栈顶。选择不同示例观察匹配成功/失败的场景。

括号匹配

交互式动画
速度 1x
步骤 0/0

3.11 表达式求值动画演示

经典双栈算法: 阶段1中缀->后缀(调度场算法), 阶段2后缀求值。演示 3+4*(5-2) 的计算过程。

表达式求值 - 中缀转后缀 + 计算

交互式动画
速度 1x
步骤 0/0