(c3)栈应用:栈混洗

官官方信息技术老师·17 页·深入(追求细节与边界)·0 次浏览·38 天前
栈混洗出栈序列Catalan数判定算法

栈应用:栈混洗

看完你能判断任意序列能否由栈混洗得到,并数出合法方案数

按 空格/→ 演示下一步

问一句
1 / 17 页

全部页面点击任意一页,跳回舞台从这页播放

栈混洗出栈序列Catalan数判定算法

栈应用:栈混洗

看完你能判断任意序列能否由栈混洗得到,并数出合法方案数

1第 1 页 · 栈应用:栈混洗

什么是混洗

上一页我们把元素压进栈、弹出来,顺序会反过来。那如果允许「压一个、弹一个」交替进行呢?就像发牌人发一张就能打一张,出来的顺序就不止一种了。

操作规则
1..n 依次进栈,任何时刻都可以选择弹出栈顶
输出序列
全部元素出栈后的排列顺序,就是一次混洗结果
不唯一
进栈顺序固定,出栈时机自由,结果有多个
发牌入栈再出栈对应 →栈混洗

发牌人按顺序发牌对应进栈,何时打出对应弹出

2第 2 页 · 什么是混洗

一个 n=3 的完整例子

用 n=3 走一遍完整混洗:进进进出进出,看 2 3 1 怎么诞生。

1
1号进栈
1 压入栈中,还压着不出,等待后续元素先出来
2
2号进栈
2 压在 1 上面;此时栈内自底向顶为 1、2
3
3号进栈
3 最后入栈,落在栈顶,进栈动作到此全部完成
4
3号出栈
弹出栈顶 3,输出序列首位是 3
5
2号出栈
栈顶变为 2,弹出 2,输出 3、2
6
1号出栈
弹出最后的 1,得到混洗结果 3 2 1
3第 3 页 · 一个 n=3 的完整例子

混洗与操作序列

每个操作序列唯一确定一个混洗结果;对比 312 与 231 两条链,看 push/pop 交错顺序如何决定输出。

图解渲染中…
b1:先把 1、2、3 依次全部入栈e1:入栈到 2 时先弹 2,再入 3a1:n 个 push 与 n 个 pop 的交错排列
4第 4 页 · 混洗与操作序列

先数操作序列

上一页我们已经确认:每一次栈混洗,都唯一对应一串 push 和 pop 操作。要数清楚有多少种混洗,最自然的想法是把每种操作序列当成一个符号串,先弄清「哪些串是合法的」,再数合法串的个数。

操作变符号
把 push 记作 A、pop 记作 B,一次混洗就是一个长为 2n 的 AB 串
合法性条件
pop 前栈里必须有元素,故任意前缀中 B 的个数不超过 A
计数目标
问题变成:数出满足前缀条件的 2n 长 AB 串有多少个
合法括号串对应 →合法操作序列

A 对应左括号、B 对应右括号,前缀合法恰好等价于括号随时匹配得上

5第 5 页 · 先数操作序列

卡特兰数

上一页我们数了 n=3 时共有 16 条合法操作序列,但其中两条会撞出相同的混洗——合法混洗其实只有 5 种。那 n=4、n=5 呢?逐个枚举太笨了,数学家找到了一个通用的计数公式。

卡特兰数
n 个元素的合法栈混洗个数,记作 catalan(n)
计数公式
等于 (2n)! 除以 n! 与 (n+1)! 的乘积
n=3 验证
代入得 6!/ (3!·4!) = 720/144 = 5,与枚举一致
走格子只许右或上对应 →合法操作序列

每条从角落到对角的路径对应一种进出栈顺序

catalan(n)=(2n)!n! (n+1)!⇒catalan(3)=6!3! 4!=5catalan(n)=\frac{(2n)!}{n!\,(n+1)!}\quad\Rightarrow\quad catalan(3)=\frac{6!}{3!\,4!}=5
6第 6 页 · 卡特兰数

n=3 的五种混洗枚举

中心是混洗对象,五个分支是 n=3 的全部合法输出序列,外加一个禁例做对照。

图解渲染中…
栈不交叉:先出的元素之后,比它小的都必须降序输出3先出则1先于2:任何输出中不会出现 …3…2…1…之外的顺序颠倒312:3 先出意味着 1、2 都在栈中,2 必先于 1 出栈,故 312 非法
7第 7 页 · n=3 的五种混洗枚举

对比:全排列有 3!=6 种

上一页我们枚举了 n=3 的五种混洗。但 3 个元素的排列明明有 3!=6 种——多出来的那一个是谁?把两份清单并排放一起对比,缺失的那个立刻现形。

全排列 6 种
3! = 6 个排列,是所有可能顺序的总集合
混洗 5 种
合法操作序列只有 5 条,对应 5 个排列
缺失的 312
6 减 5,恰好 312 无法由任何操作序列产生
甄别问题
给定一个排列,判断它是否为合法混洗
持证司机名单对应 →混洗集合

不是每个会开车的人都有证:6 个排列中只有 5 个拿到「合法」资格

8第 8 页 · 对比:全排列有 3!=6 种

禁形 312

上页我们数出了 n=3 只有 5 种混洗,全排列 6 种里恰好有一种「混不出来」。它长什么样?把 abc 变成 cab 时,先弹 c 再弹 a 就卡住了——因为 b 还压在 a 上面。这个卡住的形状叫 312 模式。

312 模式
存在 i<j<k,取值满足 a_k < a_i < a_j,如 cab 中 3、1、2
为何卡住
想先取 k(最小),必须先弹出比它大的 a_j,a_j 就会落在 a_i 前面
判定定理
序列是栈混洗,当且仅当它不含 312 模式
排队取餐对应 →栈混洗

队首的餐(最小值)被后面的人压着,必须让排在后面的人先取走

可混洗  ⟺  ∄ i<j<k: ak<ai<aj\text{可混洗} \iff \nexists\, i<j<k:\ a_k < a_i < a_j
9第 9 页 · 禁形 312

为什么 312 做不到

逐步推演:想最先输出 3,1 和 2 只能先被压进栈里。

1
必须先出3
3 要第一个输出,而入栈顺序只能是 1、2、3
2
1、2被迫入栈
3 出栈前,1、2 必须已压在栈中
3
2先出
3 出栈后,栈顶是 2,栈顶只能先出
4
1随后出
此时栈里只剩 1,出栈得到 21,而非 12
5
312被排除
唯一能得 3 开头的序列是 321,312 不可能
10第 10 页 · 为什么 312 做不到

用 312 判定具体序列

跟着流程走一遍:取序列、枚举子序列、比对 312 模式,看判定如何落地。

图解渲染中…
c1:长度3的序列只有1个子序列:它自己d1:比较数值大小顺序,不是逐字符相等
11第 11 页 · 用 312 判定具体序列

栈模拟甄别算法

用辅助栈逐个压入 1..n,贪心对齐目标序列,即可判定混洗合法性。

1
初始化空栈
清空辅助栈,指针指向目标序列第一项,准备逐个压入 1..n
2
压入下一数
按顺序把 1..n 中下一个数压栈,模拟唯一允许的入栈次序
3
栈顶对齐即弹
只要栈顶等于目标序列当前项就弹出,指针后移,贪心消耗目标
4
循环至输入耗尽
重复压栈—弹栈,直到 1..n 全部压入完毕
5
查栈空判合法
结束后栈为空说明目标全部匹配,合法;栈非空即含 312 禁形
12第 12 页 · 栈模拟甄别算法

算法实现

python

用一段 20 行的贪心模拟,把上一页的甄别策略变成可运行的判定程序。

代码高亮加载中…

高亮行正是贪心策略:能进栈就进栈,栈顶等于目标就弹出,两边都不满足则命中禁形 312。

13第 13 页 · 算法实现

括号:混洗与匹配的对应

上一页我们用栈模拟甄别混洗序列。现在换个视角:把每个 push 写成左括号「(」,pop 写成右括号「)」,一段操作序列就变成一串括号——而合法混洗,恰好对应合法匹配。

操作即括号
push 记 (、pop 记 ),序列变成括号串
匹配即合法
任意前缀中 ( 数 ≥ ) 数,总数相等,栈才不空弹
计数再遇卡特兰
n 对括号的合法匹配数正是 Catalan 数 Cₙ
括号配对对应 →栈混洗

未闭合的左括号对应栈中暂存元素,右括号对应弹出

push/pop 序列数=Cn=1n+1(2nn)\text{push/pop 序列数}=C_n=\frac{1}{n+1}\binom{2n}{n}
14第 14 页 · 括号:混洗与匹配的对应

自测:甄别一个序列

点击作答

入栈顺序为 1,2,3,4,下列哪个出栈序列是合法的栈混洗?

15第 15 页 · 自测:甄别一个序列

收束要点

  • ✓n 个元素的混洗数是卡特兰数 C(2n,n)/(n+1)
  • ✓每个混洗唯一对应一个 push/pop 操作序列
  • ✓可实现的混洗当且仅当不含 312 模式
  • ✓判定方法:模拟入栈出栈,贪心匹配
  • ✓栈混洗与合法括号序列本质同源
延伸主题: Catalan 数的更多组合场景用树/图刻画混洗偏序关系推广:多栈与受限队列混洗
16第 16 页 · 收束要点

课后思考

先自己想一会儿,再对照参考答案——答案只给路径,不给终点。

1栈混洗计数为什么恰好是卡特兰数?试着从操作序列的约束自己推一遍。

参考答案任一前缀中 push 数不少于 pop 数,且总数相等——这类路径正是卡特兰数的定义。可从 n=3 的 5 种逐步归纳到递推式 C(n)=ΣC(i)C(n-1-i)。

2如果输入序列本身不是 1..n,禁形 312 的判定还成立吗?

参考答案312 禁形基于输入有序的相对大小关系,判据本质是「不存在 i<j<k 使输出呈大-小-中」,用秩代替数值即可,结论仍然成立。

3把一个栈换成两个栈,可实现的输出排列会不会突破卡特兰数?边界在哪?

参考答案会突破:两栈的表达能力更强,某些 312 禁形也可能实现。但并非全排列都可实现,双栈可排序排列计数仍是开放性研究话题,可查 Knuth 相关讨论。

17第 17 页 · 课后思考
(c3)栈应用:栈混洗 · 知识图解