栈 (stack) 是将插入与删除操作限定在一端进行的线性表, 这一端称为栈顶 (top), 另一端称为栈底 (bottom). 不含元素的栈称为空栈. 栈是一种后进先出 (LIFO, last in first out) 的结构.
顺序栈
用一数组 stack
保存栈的元素, 变量 len
指示栈中元素的个数. 于是栈顶元素的位置为
stack[len-1]
.
struct Stack: Type stack[N] int len init(): len = 0 is_empty(): return len == 0 is_full(): return len == N # 使用前保证栈非空 top(): return stack[len-1] # 使用前保证栈非空 pop(): return[--len] push(val): if is_full(): error('overflow') stack[len++] = val
链栈
用一单链表保存栈的元素, 删除与插入都在表头进行.
用于指示栈中元素数目的变量 len
是可选的.
链栈克服了顺序栈可能溢出的缺点.
struct StackNode: Type val StackNode *next StackNode *head init(): head = NULL is_empty(): return head == NULL # 使用前保证栈非空 top(): return head->val # 使用前保证栈非空 pop(): tmp = head val = tmp->val head = head->next; delete tmp return val push(val): head = new StackNode(val, head) # 头插法
考虑数字 1 在出栈序列中的位置 k. 整个过程描述如下: 数字 1 进栈; `k-1` 个数字进栈后又出栈: `C_(k-1)` 种组合; 数字 1 出栈; `n-k` 个数字进栈后又出栈: `C_(n-k)` 种组合; 因此全部的组合数目是 `C_n = sum_(k=1)^n C_(k-1) C_(n-k)`, `quad C_0 = 1`. 这是 Catalan 数的递推关系.
求进栈序列为 1 2 3 4 时, 所有不可能的出栈序列.
所有不可能的出栈序列有 `4! - C_4 = 10` 种. 以 4 开头的包括除了 4 3 2 1 之外的 `3!-1 = 5` 种: 4 1 2 3, 4 1 3 2, 4 2 3 1, 4 2 1 3, 4 3 1 2. 以 3 开头的必然含有子序列 1 2, 再把 4 插入其中, 一共 3 种: 3 4 1 2, 3 1 4 2, 3 1 2 4. 以 2 开头和以 1 开头的各有一种: 2 4 1 3, 1 4 2 3.
队列 (queue) 是将插入与删除操作限定在两端进行的线性表, 一端只能删除元素, 称为队头; 一端只能插入元素, 称为队尾. 不含元素的队列称为空队列. 队列是一种先进先出 (FIFO, first in first out) 的结构. 队列常用于操作系统中需要等待的事务, 比如缓冲区队列, 请求队列等.
顺序队列 (循环队列) 充分利用数组的空间,
一个大小为 N+1
的数组最多能容纳 N
个元素
, 其中下标 i
的下一个下标为
next(i) := (i+1) % (N+1)
.
用指针 front
指示队头元素, rear
指示队尾元素的下一个位置, 这个位置不保存元素.
其中 front == rear
表示队列空, front ==
next(rear)
表示队列满.
struct Queue: Type queue[N+1] int front int rear next(i): return (i+1) % (N+1) init(): front = rear = 0 is_empty(): return front == rear is_full(): return front == next(rear) # 使用前保证队列非空 front(): return queue[front] # 使用前保证队列非空 dequeue(): val = queue[front] front = next(front) return val enqueue(val): if is_full(): error('overflow') queue[rear] = val rear = next(rear)
链式队列 用一单链表表示队列,
head
指示队头元素,
tail
指示队尾元素. 以 head == NULL
表示空队列, 注意空队列时, tail
的取值无意义.
链式队列克服了顺序存储队列可能溢出的缺点.
struct QueueNode: Type val QueueNode *next struct LinkQueue: QueueNode *head QueueNode *tail init(): head = NULL is_empty(): return head == NULL # 使用前保证队列非空 front(): return head->val # 使用前保证队列非空 dequeue(): tmp = head val = tmp->val head = head->next delete tmp return val enqueue(val): if is_empty(): head = tail = new QueueNode(val, NULL) else: # 尾插法 tail = tail->next = new QueueNode(val, NULL)
链式队列与链栈的实现代码几乎一样,
除了链式队列的 enqueue
需要先判断队列非空, 在非空情形下用尾插法.
而链栈的 push
直接从头部插入即可;
设数字 1 到 n 以某一次序排成一队列, 将序列分成 k 个不相交子序列的并, 使得每个子序列都呈升序 (子序列中的相邻元素在原序列中不一定相邻). 再作 k 路归并, 可得到 1 到 n 的升序队列. 求 k 的最小值. 如序列 8 4 2 5 3 9 1 6 7 可分为 4 个子序列 8 9, 4 5 6 7, 2 3 和 1.
容易知道, 所求的三类出队序列均不能由栈得到, 即这三类出队序列必在中 10 个序列当中. 在这些序列中验证可知, 不能由输入受限的双端队列得到的出队序列有 4 2 3 1, 4 2 1 3; 不能由输出受限的双端队列得到的出队序列有 4 1 3 2, 4 2 3 1. 因此题 1. 的答案是 4 1 3 2; 题 2. 的答案是 4 2 1 3; 题 3. 的答案是 4 2 3 1.
后缀表达式可以视为后序遍历表达式树的结果 (见二叉树一章),
同一表达式的后缀版本和中缀版本, 操作数的次序完全相同.
类似地, 前缀和中缀表达式分别是前序和中序遍历表达式树的结果.
如下面的表达式树, 其前, 中, 后缀表达式分别为:
+/ab/-*cd*efg
,
a/b+(c*d-e*f)/g
和
ab/cd*ef*-g/+
.
+ / \ ÷ ÷ / \ / \ a b - g / \ * * / \ / \ c d e f
后缀表达式求值 设一操作数栈. 顺序扫描表达式, 若是数字则入栈; 若是运算符 op, 则从栈中先后取出操作数 y 和 x, 并执行 x op y, 将结果存入栈中. 处理完表达式后, 栈顶元素就是计算的结果.
a/b+(c*d-e*f)/g
时,
扫描到 f
的时候, 栈中的元素是 (从底到顶):
+(-*
.
算符优先法 直接处理中缀表达式. 规定前后相邻两个运算符 `theta_1`, `theta_2` 间的优先级如下表. 为了先计算括号内, 再计算括号外, 规定加减乘除的优先级均低于左括号, 但高于右括号. 另外加减的优先级低于乘除, 而加减之间 (或乘除之间) 是从左往右计算的, 所以同一运算符, `theta_1` 优先于 `theta_2`. 令左括号与右括号的优先级 "相等", 表示括号相匹配. EOL 为行结束符, 我们在表达式左端也虚设一个 EOL, 和结尾的 EOL 相匹配. N 表示不可比较, 遇到这种情况说明表达式出错.
`theta_1 \\ theta_2` | + | - | * | / | ( | ) | EOL |
+ | > | > | < | < | < | > | > |
- | > | > | < | < | < | > | > |
* | > | > | > | > | < | > | > |
/ | > | > | > | > | < | > | > |
( | < | < | < | < | < | = | N |
) | > | > | > | > | N | > | > |
EOL | < | < | < | < | < | N | = |
我们可以用数字 0-6 表示这些运算符, 并用
char precede[7][7]
保存这个优先级表. 算法设两个栈, operator
保存算符,
operand
保存值.
struct Token: int type int val eval(): stack operator, operand operator.push(EOL) t = get_token() while operator: if t.type == NUM: operand.push(t.val) t = get_token() else: # t.type == OP prec = precede[operator.top()][t.val] if prec == '<': operator.push(t.val) t = get_token() elif prec == '=': operator.pop() t = get_token() elif prec == '>': theta = operator.pop() b = operand.pop() a = operand.pop() res = calculate(theta, a, b) operand.push(res) else: error() return operand.top()
min 栈 用双倍的空间, 可以在 O(1) 的时间求得栈内元素的最小值.
struct MinStack: Stack min Stack main init(): min.init() main.init() push(val): if min.is_empty(): min.push(val) else: min.push(min(min.top(), val)) main.push(val) pop(val): min.pop() return main.pop() # 使用前保证栈非空 get_min(): return min.top()
双栈模拟的队列
栈 s1
用于入队, 栈 s2
用于出队.
如果 s1
满而 s2
空, 则将 s1
的元素转移到 s2
. 如果 s1
满而 s2
非空, 则队列已满.
Stack s1, s2 is_empty(): return s1.is_empty() and s2.is_empty() move(): while !s1.is_empty(): s2.push(s1.pop()) enqueue(val): if s1.is_full(): if s2.is_empty(): move() else: error('overflow') s1.push(val) # 使用前保证队列非空 dequeue(): if s2.is_empty(): move() return s2.pop()