第 4 章 栈和队列
在第 3 章学习了对于线性表的各种操作,本章将要介绍的栈和队列,都是特殊的线性表,即操作受限的线性表。这种特殊的数据结构,主要是针对某些特殊需要的场景而设置。
4.1 栈
如果对与一般的线性表进行频繁的插入和删除操作,若采用顺序存储结构,则都是 O(n)O(n)O(n) 时间复杂度的操作。但是,对于顺序表而言,若在表尾进行插入和删除,则其时间复杂度是 O(1)O(1)O(1) ,不需要移动元素。由此就可以构造一种特殊的线性表——栈。
栈(Stack)是一种只能在一端进行插入或删除操作的线性表。
- 允许进行插入、删除操作的一端称为栈顶(Top)。
- 另一端称为栈底(Bottom)。
不含元素的空表称为空栈。
- 栈的插入操作,称为进栈或入栈(Push)。
- 栈的删除操作,称为出栈或退栈(Pop)。
假设有栈 SSS ,如有序列 1、2、3 依次入栈和出栈,根据上述操作,可能会有如下情况:
- 1 入栈(不出栈),2 入栈(不出栈),3 入栈(不出栈),此时栈顶元素是 3;然后三个数字依次出栈,于是得到了出栈序列:3、2、1。
- 1 入栈,而后出栈,并加入到出栈序列(此时出栈序列是:1);2 入栈(不出栈),3 入栈(不出栈);此时栈顶元素是 3,栈内共有两个元素,这两个元素再依次出栈,并分别加入到出栈序列中,于是得到出栈序列:1、3、2。
- 1 入栈(不出栈);2 入栈,此时栈顶元素是 2,此元素出栈,并加入到出栈序列中(此时出栈序列是:2);2 出栈后,栈顶元素是 1,它再出栈,并加入到出栈序列(此时出栈序列是:2、1);3 入栈,并且出栈后加入到出栈序列,于是得到出栈序列:2、1、3。
- 等等,还有其他入栈、出栈的组合,分别得到不同的出栈序列。
假设按 a1,a2,⋯ ,ana_1,a_2,\cdots,a_na1,a2,⋯,an 的次序依次进栈(没有出栈),则可以得到栈出栈 S=(a1,a2,⋯ ,an)S=(a_1,a_2,\cdots,a_n)S=(a1,a2,⋯,an) ,如图 4.1.1 所示,其中第一个进栈的元素 a1a_1a1 在栈底,称之为栈底元素;最后一个进栈的元素 ana_nan 在栈顶,称之为栈顶元素。显然,在出栈的时候,后入栈的 ana_nan 先出栈,这种栈的特点概括为:后进先出(Last In First Out, LIFO)。
结论:证明:假设有 nnn 个不同元素组成的序列,通过一个栈产生的出栈序列个数为 1n+1C2nn\frac{1}{n+1}C_{2n}^nn+11C2nn 。
证明
设 ana_nan 表示 nnn 个不同自然数序列通过栈产生的出栈序列个数。定义 a0=1a_0 = 1a0=1(空序列)。
考虑元素 111 在出栈序列中的位置。元素 111 出栈时,假设此时已入栈的元素个数为 kkk(包括元素 111 自身),其中 kkk 的取值范围为 111 到 nnn。则出栈序列的排列是:
- 元素 1 之前由元素 2,3,⋯ ,k2,3,\cdots,k2,3,⋯,k 构成的出栈序列,这部分有 k−1k-1k−1 个元素,且栈初始为空(元素 111 被压入时栈为空,但元素 111 在弹出前保持在栈底,不影响其上元素的操作)。假设这部分元素所构成的序列个数为 ak−1a_{k-1}ak−1。
- 元素 1 之后是由元素 k+1,k+2,…,nk+1, k+2, \dots, nk+1,k+2,…,n 出栈所构成的序列,这部分有 n−kn-kn−k 个元素,栈初始为空(元素 111 被弹出后栈为空)。假设这部分序列个数为 an−ka_{n-k}an−k。
对于每个固定的 kkk,两部分独立,且元素 111 出栈时的序列贡献为 ak−1⋅an−ka_{k-1} \cdot a_{n-k}ak−1⋅an−k。由于 kkk 可取 111 到 nnn,总和为:
an=∑k=1nak−1an−k
a_n = \sum_{k=1}^{n} a_{k-1} a_{n-k}
an=k=1∑nak−1an−k
令 i=k−1i = k - 1i=k−1,则当 k=1k = 1k=1 时 i=0i = 0i=0,当 k=nk = nk=n 时 i=n−1i = n-1i=n−1,因此:
an=∑i=0n−1aian−1−i(1)
a_n = \sum_{i=0}^{n-1} a_i a_{n-1-i}\tag{1}
an=i=0∑n−1aian−1−i(1)
- 当 n=0n = 0n=0 时,a0=1a_0 = 1a0=1(空序列)。
- 当 n=1n = 1n=1 时,元素 111 入栈后弹出,唯一序列为 ⟨1⟩\langle 1 \rangle⟨1⟩,故 a1=1a_1 = 1a1=1。
下面考虑 Catalan 数的递归定义:
Cn=1n+1(2nn),C0=1
C_n = \frac{1}{n+1} \binom{2n}{n}, \quad C_0 = 1
Cn=n+11(n2n),C0=1
并满足递归关系:
Cn=∑i=0n−1CiCn−1−i,n≥1(2)
C_n = \sum_{i=0}^{n-1} C_i C_{n-1-i}, \quad n \geq 1\tag{2}
Cn=i=0∑n−1CiCn−1−i,n≥1(2)
下面证明(1)和(2)式的等价性,比较 ana_nan 和 CnC_nCn:
- 初始条件相同:a0=1=C0a_0 = 1 = C_0a0=1=C0。
- 递归关系相同:an=∑i=0n−1aian−1−ia_n = \sum_{i=0}^{n-1} a_i a_{n-1-i}an=∑i=0n−1aian−1−i,与 Cn=∑i=0n−1CiCn−1−iC_n = \sum_{i=0}^{n-1} C_i C_{n-1-i}Cn=∑i=0n−1CiCn−1−i 一致。
因此,由数学归纳法,对所有 n≥0n \geq 0n≥0,有 an=Cna_n = C_nan=Cn。
由 Catalan 数的闭式表达:
an=Cn=1n+1(2nn)
a_n = C_n = \frac{1}{n+1} \binom{2n}{n}
an=Cn=n+11(n2n)
故 nnn 个不同元素的序列通过一个栈产生的出栈序列个数为 1n+1(2nn)\frac{1}{n+1} \binom{2n}{n}n+11(n2n)。
证毕。
验证小规模情况
- n=0n = 0n=0:a0=1a_0 = 1a0=1,11(00)=1\frac{1}{1} \binom{0}{0} = 111(00)=1。
- n=1n = 1n=1:序列 ⟨1⟩\langle 1 \rangle⟨1⟩,a1=1a_1 = 1a1=1,12(21)=1\frac{1}{2} \binom{2}{1} = 121(12)=1。
- n=2n = 2n=2:序列 ⟨1,2⟩,⟨2,1⟩\langle 1, 2 \rangle, \langle 2, 1 \rangle⟨1,2⟩,⟨2,1⟩,a2=2a_2 = 2a2=2,13(42)=2\frac{1}{3} \binom{4}{2} = 231(24)=2。
- n=3n = 3n=3:序列 ⟨1,2,3⟩,⟨1,3,2⟩,⟨2,1,3⟩,⟨2,3,1⟩,⟨3,2,1⟩\langle 1, 2, 3 \rangle, \langle 1, 3, 2 \rangle, \langle 2, 1, 3 \rangle, \langle 2, 3, 1 \rangle, \langle 3, 2, 1 \rangle⟨1,2,3⟩,⟨1,3,2⟩,⟨2,1,3⟩,⟨2,3,1⟩,⟨3,2,1⟩,a3=5a_3 = 5a3=5,14(63)=5\frac{1}{4} \binom{6}{3} = 541(36)=5。
结果一致,进一步验证结论。
例 4.1.1 若让元素 1,2,3,4,5 依次进栈,则出栈次序不可能出现在( )种情况。
A. 5,4,3,2,1 \qquad B. 2,1,5,4,3
C. 4,3,1,2,5 \qquad D. 2,3,5,4,1
【解】
栈是后进先出的线性表,不难发现 C 选项中元素 1 比元素 2 先出栈,违背了栈的后进先出原则,所以不可能出现 C 选项所示的情况。
本题答案:C
例 4.1.2 若已知一个栈的入栈序列是 1,2,3,…,n,其输出序列为 p1,p2,p3,…,pnp_1,p_2,p_3,…,p_np1,p2,p3,…,pn,若 p1=np_1=np1=n,则 pip_ipi 为( )。
A. iii \qquad B. n−in-in−i C. n−i+1n-i+1n−i+1\qquadD. 不确定
【解】
栈是后进先出的线性表,一个栈的入栈序列是 1,2,3,…,n,而输出序列的第一个元素为 n,说明 1,2,3,…,n一次性全部进栈,再进行输出,所以 p1=n,p2=n−1,…,pi=n−i+1p_1=n,p_2=n-1,…,p_i=n-i+1p1=n,p2=n−1,…,pi=n−i+1 。
本题答案:C