系列文章的导航链接:
张浩驰:《趣味算法》专栏所有文章分类 - 导航zhuanlan.zhihu.com下篇文章Part 2导航 - 队列混洗问题:
张浩驰:计算机程序设计艺术(TAOCP)精读笔记3 - 不讲一般的线性表 Part2 - 双端队列混洗问题zhuanlan.zhihu.com正文:
作者:@张靖昆
编辑:@张浩驰
一、前言
前两个精读笔记,我简单介绍了算法分析应该做到怎样的程度,以及比较全面的复杂度标记符号和斯特林公式的数学思想,但是在理论计算机的复杂度分析中,复杂度标记符号不仅仅是这些,还有一些其他的情况,这些我们暂且不讨论。
不得不说,前两个笔记着实有些吓人,那公式一大堆一大堆的,我以为我在学数学。但现在想想,我确实也就是在学数学。。。。但!!!从现在起,我们开始讨论基本的数据结构,当然,由于TAOCP针对的读者是有一定数据结构和算法基础的人,因此我不讨论过于基础的东西,例如栈、队列的定义。我们只讨论基于这些数据结构的一些定理和算法!
当然,也欢迎新手同学一起探讨,但是需要补一补基础,这里探讨的线性表是更深的拓展。
二、Permutations1 - 栈混洗问题 - 以Stack为中转容器
这里我们探讨线性表和全排列的关系。
问题描述:以一个线性序列1...n作为输入,如果以栈为中转容器,并且允许进栈和出栈操作在何时何地都能进行(当然除了空栈和满栈),那么最终当所有序列都进入栈并弹出后,得到的序列会有很多种。这里就衍生出了关于Permutation的问题。
这个问题和很多经典问题类似,例如同样一个n元素线性序列可以构造的不同的二叉搜索树的数量,再比如将括号插入公式的不同方式有多少种、将一个多边形分成三角形的不同方式有多少种等。感兴趣的,大家可以自行查询,这些问题在本质上应该是与排列问题相似的。
问题1:Permutation的数量有多少?- 经典公式:卡特兰数
这个问题在清华计算机系邓俊辉教授(邓公)所著的《数据结构》及配套习题中有讲解,邓公的这本数据结构,我认为是《算法导论》级别的著作,非常好,建议大家好好仔细的学习一下。我认为可能是国内最好的数据结构教材了,至少我非常非常喜欢。邓公是计算机图形学方向的大佬,对色彩的把控能力非常厉害,这本书就是艺术品,应该改名成《数据结构的艺术》,哈哈哈。
邓公所提及的方法[习题4-4 b]是总结了“栈混洗”的规律,然后给出了递推公式,并说明了是卡特兰数的递推公式。当然,更详细的讲解,大家可以看学堂在线邓公的数据结构公开课。如下是邓公《数据结构》的主页,邓公出品,必属精品!
DSACPPdsa.cs.tsinghua.edu.cn***这里我们采用Donald所提及的方法,即寻找不合法序列个数的方法:可以肯定的是,进栈操作和出栈操作必各有
基于这样的想法,首先可以肯定的是,去除不合法序列之前的全排列序列个数的求解是一个多重集的全排列问题,共
对于任意一个不合法序列,一定具有“出栈操作数量大于入栈操作数量”的特征。那么很容易的可以想到,对于这种情况,从左到右(程序从左向右执行)的第1个满足这个特征的出栈操作一定是非常重要的,它的性质很重要。
在这里,这个出栈操作会导致以第1个操作为beginning,并以它为ending的子序列中,出栈操作数量比入栈操作数量刚好多1。而这个子序列是判定一个序列合不合法的最小特征,也是最基本的特征,它覆盖了全部不合法序列。例如一个不合法序列可能有或没有 出栈操作数量比入栈操作数量多2,3,4等等的子序列,但是一定会有 出栈操作数量比入栈操作数量多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
那么现在的问题就是:“不合法”序列的数量和由
因为对于任意一个由
“合法”序列的数量就是:
这个方法体现的是“反射原理”,利用反射原理,从而将一个问题转化为了另一个等价的问题进行求解!
问题2:一个序列是栈混洗序列的充要条件 - 来自邓公的《数据结构》习题[4-3]
序列B是序列A(元素完全相同的两个序列都是从1-n的n个互不相同的自然数,其他序列类推)的一个栈混洗,当且仅当,对任意的
证明:
先证明“仅当”即从栈混洗已经成立,推出不可能包含题目中所述模式。可以采用反证法:
首先,对于输入序列中的任意3个元素,其在输出序列中,是否存在一个可行的相对排列次序是和其他元素无关的,这是很明显的,因此我们只需要关注
接下来,无论如何,元素i和j必然先于k压入中转栈,这是由进栈顺序决定的。假如序列
再证明当:
恩,,没有找到数学证明,哈哈,通过一个算法就可以证明了。
上述截图是邓公数据结构习题中关于充要条件的证明。
这个算法只需要略作修改就可以变成对栈混洗的证明,比如说每次弹出的元素和原始序列进行比较就行了。
我相信你感受到了,这里目前没有提及数学上的问题,这些都是算法的问题,因此对于算法的证明,是很重要的,因此我之后会补一个文章,专门讨论如何证明算法的正确性!!当算法被证明是正确的,那么问题也就得到了解决!