图解全排列问题_计算机程序设计艺术(TAOCP)精读笔记3 - 不讲一般的线性表 Part 1 - 栈混洗问题...

d84c398d8c209a6273bd536942398e04.png

系列文章的导航链接:

张浩驰:《趣味算法》专栏所有文章分类 - 导航​zhuanlan.zhihu.com
89ed44d5dc840effb17575e242ddb28b.png

下篇文章Part 2导航 - 队列混洗问题:

张浩驰:计算机程序设计艺术(TAOCP)精读笔记3 - 不讲一般的线性表 Part2 - 双端队列混洗问题​zhuanlan.zhihu.com
6ba06786b19021ebdaca32d77d951ee1.png

正文:

作者:@张靖昆

编辑:@张浩驰

一、前言

前两个精读笔记,我简单介绍了算法分析应该做到怎样的程度,以及比较全面的复杂度标记符号和斯特林公式的数学思想,但是在理论计算机的复杂度分析中,复杂度标记符号不仅仅是这些,还有一些其他的情况,这些我们暂且不讨论。

不得不说,前两个笔记着实有些吓人,那公式一大堆一大堆的,我以为我在学数学。但现在想想,我确实也就是在学数学。。。。但!!!从现在起,我们开始讨论基本的数据结构,当然,由于TAOCP针对的读者是有一定数据结构和算法基础的人,因此我不讨论过于基础的东西,例如栈、队列的定义。我们只讨论基于这些数据结构的一些定理和算法!

当然,也欢迎新手同学一起探讨,但是需要补一补基础,这里探讨的线性表是更深的拓展。

17fd8e2c043bb19806b3b38f22ae6e70.png

二、Permutations1 - 栈混洗问题 - 以Stack为中转容器

这里我们探讨线性表和全排列的关系。

问题描述:以一个线性序列1...n作为输入,如果以栈为中转容器,并且允许进栈和出栈操作在何时何地都能进行(当然除了空栈和满栈),那么最终当所有序列都进入栈并弹出后,得到的序列会有很多种。这里就衍生出了关于Permutation的问题。

这个问题和很多经典问题类似,例如同样一个n元素线性序列可以构造的不同的二叉搜索树的数量,再比如将括号插入公式的不同方式有多少种、将一个多边形分成三角形的不同方式有多少种等。感兴趣的,大家可以自行查询,这些问题在本质上应该是与排列问题相似的。

问题1:Permutation的数量有多少?- 经典公式:卡特兰数

这个问题在清华计算机系邓俊辉教授(邓公)所著的《数据结构》及配套习题中有讲解,邓公的这本数据结构,我认为是《算法导论》级别的著作,非常好,建议大家好好仔细的学习一下。我认为可能是国内最好的数据结构教材了,至少我非常非常喜欢。邓公是计算机图形学方向的大佬,对色彩的把控能力非常厉害,这本书就是艺术品,应该改名成《数据结构的艺术》,哈哈哈。

邓公所提及的方法[习题4-4 b]是总结了“栈混洗”的规律,然后给出了递推公式,并说明了是卡特兰数的递推公式。当然,更详细的讲解,大家可以看学堂在线邓公的数据结构公开课。如下是邓公《数据结构》的主页,邓公出品,必属精品!

DSACPP​dsa.cs.tsinghua.edu.cn
c42dc2264a6abf0e1138e31b2f417e88.png

***这里我们采用Donald所提及的方法,即寻找不合法序列个数的方法:可以肯定的是,进栈操作和出栈操作必各有

个,而我们要的结果就是这
个操作混在一起所组成的Permutation中,合法的有多少个,即可以组成合法序列的操作序列有多少个?

基于这样的想法,首先可以肯定的是,去除不合法序列之前的全排列序列个数的求解是一个多重集的全排列问题,共

个,
接下来,我们要做的就是找到所有不合法序列的个数

对于任意一个不合法序列,一定具有“出栈操作数量大于入栈操作数量”的特征。那么很容易的可以想到,对于这种情况,从左到右(程序从左向右执行)的第1个满足这个特征的出栈操作一定是非常重要的,它的性质很重要。

在这里,这个出栈操作会导致以第1个操作为beginning,并以它为ending的子序列中,出栈操作数量比入栈操作数量刚好多1而这个子序列是判定一个序列合不合法的最小特征,也是最基本的特征,它覆盖了全部不合法序列。例如一个不合法序列可能有或没有 出栈操作数量比入栈操作数量多2,3,4等等的子序列,但是一定会有 出栈操作数量比入栈操作数量多1的子序列。大家要尝试形成这样的想法,第1个和最后一个总是有一定的特殊性。

**重点理解:在这里,第1个满足前述特征的出栈操作具有的性质就是:在这个出栈操作之前,出栈操作数量和进栈操作数量相等。而如果我们将包含这个出栈操作和它前面的所有操作在内的所有操作做一个对调:即将出栈操作变为进栈操作,进栈操作变为出栈操作,那么我们将得到一个拥有

个进栈操作和
个出栈操作的序列并且任意一个“不合法”序列都可以做到。如下所示,加粗的就是第1个满足前述特征的出栈操作:

push, push, pop, push, pop, pop, pop, push, push, pop, pop, pop

该序列按照上述规则,可变为下面这个序列:

pop, pop, push, pop, push, push, push, push, push, pop, pop, pop

那么现在的问题就是:“不合法”序列的数量和由

个进栈操作和
个出栈操作组成的拥有
个操作的序列的全排列数量一样吗?答案是肯定的。

因为对于任意一个由

个进栈操作和
个出栈操作组成的序列的全排列,从左到右我们一定可以找到第1个
进栈操作,使得以它为结尾的操作序列满足进栈操作比出栈操作数量刚好多1(跟前面提及的规则相似),那么我们做相同的对调操作,就可以得到一个由
个出栈操作和
个进栈操作组成的不合法序列。相应的,任意一个由
个出栈操作和
个进栈操作组成的不合法序列,同样可以转为任意1个由
个进栈操作和
个出栈操作组成的序列的全排列,因此:

“合法”序列的数量就是

这个方法体现的是“反射原理”,利用反射原理,从而将一个问题转化为了另一个等价的问题进行求解!

问题2:一个序列是栈混洗序列的充要条件 - 来自邓公的《数据结构》习题[4-3]

序列B是序列A(元素完全相同的两个序列都是从1-n的n个互不相同的自然数,其他序列类推)的一个栈混洗,当且仅当,对任意的

都不包含如下模式:

证明

先证明“仅当”即从栈混洗已经成立,推出不可能包含题目中所述模式。可以采用反证法:

首先,对于输入序列中的任意3个元素,其在输出序列中,是否存在一个可行的相对排列次序是和其他元素无关的,这是很明显的,因此我们只需要关注

即可。

接下来,无论如何,元素i和j必然先于k压入中转栈,这是由进栈顺序决定的。假如序列

存在,则意味着这3个元素中,k必然首先从中转栈中弹出。而根据前面所说,k在即将弹出前的瞬间,i和j必然已经转入中转栈中,根据序列所示,i和j必然全部没有出栈。然而根据“后进先出”的规律,三者在中转栈中的次序必定是
,不可能是

再证明当

恩,,没有找到数学证明,哈哈,通过一个算法就可以证明了。

a65a3ab30514f77ce78232f781175b3e.png

上述截图是邓公数据结构习题中关于充要条件的证明。

这个算法只需要略作修改就可以变成对栈混洗的证明,比如说每次弹出的元素和原始序列进行比较就行了。

我相信你感受到了,这里目前没有提及数学上的问题,这些都是算法的问题,因此对于算法的证明,是很重要的,因此我之后会补一个文章,专门讨论如何证明算法的正确性!!当算法被证明是正确的,那么问题也就得到了解决!

下一Part,我们扩展栈混洗问题中的“不合法”序列数量的更一般的求解方法,这个问题的本质其实就是“Ballot Problem”即选举问题,我们在下一Part中讲述这个问题并给出基于“生成函数”的通用的求解方法。此外,基于这个通用的求解方法,我们探讨更一般的“栈混洗问题”,即基于双端队列的队列混洗问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值