排序

Published: by Creative Commons Licence

  • Tags:

链和反链

对于偏序集S,称S的子集A是链,当且仅当A组成一个全序集。若S的子集B中元素两两不可比较,那么称B是反链。

最小链覆盖是指S的一个子集族,每个元素都是链,所有元素的并为V,但是不同元素的交集为空。同样最小反链覆盖也是S的一个子集族,每个元素都是反链,所有元素的并为V,但是不同元素的交集为空。

Dilworth定理:偏序集的最小链覆盖大小等于S的最长反链的长度

证明:

记最小链覆盖的大小为n,最长反链长度为m

LS的一个最长反链,很显然多个L中的元素不能同时属于相同的链,因此最小链覆盖至少包含m个链,即nm

之后利用数学归纳法证明nm

当S的大小为01的时候,命题显然成立。

我们记sS的一个极大元,记S=S{s}。由归纳法知,S的最小链覆盖的大小与最长反链的长度相同,记作k。设S的最长链覆盖为C1,C2,,Ck,而S的所有长度为k的反链为R1,R2,,Rr。很显然,对于Ri,必定是由最长链覆盖中每个集合取一个元素组成的,我们记RiCj的唯一公共元素为xij。对于j=1,2,,k,记mj=maxi(xij)。现在考虑集合M=m1,m2,,mk,如果其中有任意两个不同元素mi,mj可以比较,不妨设mimj,记mi所在的反链为Rp,而mj所在的反链为Rq,那么可以推出xqj=Mjxpi=Mixqi,即Rq包含两个可比较元素,这是不可能的,因此我们知道M是反链。

接下来我们考虑集合S,即向S加入元素s。考虑两种情况:

1) 如果Ms中元素都不可比较,那么我们考虑集合Ms,这是一个长度为k+1的反链。而向集合中加入一个元素,最多会增加其最长反链长度1,以及最小链覆盖大小1。因此有mk+1n

2) 如果Ms中某个元素可以比较,假设sMi可以比较,设Mi所在的反链为Rj,那么我们可以推出s大于等于Rj中的所有元素,即D=Rjs也是一个链。现在考虑集合S=SD,由于S中的所有反链都少了一个元素,因此S的最长反链的长度为k1,同样利用归纳法知其最小链覆盖为{C1,C2,,Ck1}。我们将D加入得到新的最小链覆盖{C1,C2,,Ck1,D}。因此S的最小链覆盖大小一定是k,故可以得到mk=n

Dilworth对偶定理:偏序集的最小反链覆盖大小等于S的最长链长度

证明:

记最小反链覆盖的大小为n,最长链长度为m

LS的一个最长链,而L中多个元素肯定不能同时属于一个反链,因此最小反链覆盖至少含有m个反链,即我们推出了nm

我们记X1S中的极小元的集合,并从S中移除X1的元素。之后继续定义X2为S中极小元的集合,并同样从中移除。重复这个过程,直到S为空,假设过程中我们得到了k个非空集合X1,X2,,Xk。极小元的集合一定是S的一个反链,我们得到了一个大小为k的反链覆盖。同样我们从X1,X2,,Xk中分别取一个元素x1,x2,,xk,满足x1x2xk,这样我们就得到了长度为k的一个链。得到关系式nkm,结合之前得到的nm,可以推出n=m

求最长反链方法:

我们先利用最小相交路径覆盖将图建成二分图并求得最大匹配。设n为顶点数,最大匹配数为m,记l(i)表示二分图左侧第i个顶点,r(i)表示二分图右侧第i个顶点。

之后我们可以找到最小顶点覆盖,利用最小顶点覆盖得到最大独立集。对于1in,如果l(i)r(i)同时属于最大独立集,我们就将它放入到集合A中,集合A初始时为空集。在上述操作后,我们得到的集合A就是最长反链条。上面的算法的时间复杂度为O(n2.5)

用反证法证明,如果A中任意两个元素uv可比较,那么不妨设uv。这意味着l(u)r(v)之间有边,而我们知道l(u),r(u),l(v),r(v)都属于最大独立集,因此不可能发生。

现在我们证明得到的反链长度正好为nm。由于二分图共有2n个顶点,而最大独立集中有2nm个顶点。||=|A|+|{i|l(i)r(i)}|。而后者最多为n,因此推出前者至少为nm。当然反链长度是不可能超过nm的,因此得到的反链的长度恰好为nm

整除偏序关系

对于两个正整数a,b,如果ab,则认为ab。很显然这是一个偏序关系。

题目1:要求找到1n的所有整数的最长链。

由算术基本定理可知,最长链等价于找到一个数x,满足1xn,并且其分解后素因子的幂次和最大。我们可以直接找最大的数2m,满足2mn,则最长链的长度为m+1,且最长链为20,21,,2m

题目2:要求找到1n的所有整数的最小反链覆盖。

提供一道题目

m=log2n,很显然由于20,21,,2m的存在,它们需要分到不同的组中,因此至少需要分成m+1组。

下面我们给出一个将n个数正好分成m+1组的算法。根据算术基本定理,我们知道对于任意正整数x,有x=pc11pc22pckk,我们可以记deg(x)=ici。之后我们将数x分类到第deg(x)+1组即可。由于对于任意数1xn,都一定有0deg(x)m,因此最多只需要分成m+1组。

这里顺带一提,贪心算法得出的分组结果和上面的方案是相同的。

上面的做法是O(nlnn)的,但是如果我们将x分组到log2x+1组的话,可以O(n)进行分组。

字符串无穷偏序

对于字符串S,记S+表示无穷个S前后拼接得到的无穷长的字符串。

命题1:对于任意字符串A,BA+B+ABBA,这里的比较规则是字典序。

证明:

如果AB相互没有前缀关系,则命题很显然成立。下面不妨假设AB的前缀,换言之,B=AC

考虑到A+B+的大小关系由其长度为m=|A||B|的前缀共同确认,因此我们只需要保证这一段前缀中A+B+的关系即可。

如果ABBA成立。那么容易推出ACCA,之后我们有

B+=(AC)+=A(CA)+A(AC)+=A2(CA)+A|B|(AC)+

由于B+的长度为m的前缀字典序不小于A+,因此有A+B+

同理如果AB>BA成立,可以推出A+>B+

至此命题得证。

题目1:给定n个字符串,要求将这些字符串按照一定的顺序拼接起来,且要求拼接得到的字符串字典序最小。

提供一道题目

假设结果中AB相邻,且AB之前,则很显然有ABBA成立(否则我们可以交换两者的顺序得到更小字典序的结果)。考虑到ABBA等价于A+B+,而后者是全序关系,因此可以直接上排序算法。时间复杂度为O(nmlog2n),其中m为字符串长度。

拓扑序问题

给定一副有向无环图,并给所有顶点排序,满足如果存在边(u,v),则u的序号大于v的序号,那么称这个序列满足拓扑序。

题目1:生成某个n个顶点m条边的有向无环图的任意拓扑序。

可以不断地删除出度为0的顶点,这样就能的到满足条件的拓扑序。可以用队列维护所有度数为0且未被删除的顶点,时间复杂度为O(n+m)

题目2:生成某个n个顶点m条边的有向无环图的字典序最小的拓扑序。

在题目1的基础上,不断从队列中删除编号最小的顶点即可。可以将队列替换为优先队列,这样就可以得到O(nlog2n+m)的时间复杂度性能。

题目3:生成某个n个顶点m条边的有向无环图的某个拓扑序,要求保证1的序号最小,如果有多个,则保证2的序号最小,如此循环下去。

提供一道题目

考虑生成最小字典序的时候,我们贪心删除编号最小的字典序。这时候实际上等价于我们要让编号较大的顶点序号尽可能大。现在我们希望让编号较小的顶点序号尽可能小,我们可以尝试生成一个反向拓扑序,且要求这个反向拓扑序的字典序最大即可。可以用类似题目2的做法来做,时间复杂度为O(nlog2n+m)

排序操作

给定长度为n的序列,以及k个函数f1,,fn,其中每个函数会对输入序列的某个子序列进行排序。要求判断对于任意给定序列x,能否保证fn(fn1(f1(x)))返回的序列是有序的。

问题1:给定长度nm个命令,判断是否对于所有长度为n的序列,执行上述m个命令后都能得到有序序列。命令从前往后执行,每个命令指定若干个位置,将这些位置的数从小到大进行重排。其中n15m100

我们不能枚举所有的n种排列,因为数量级太大。但是我们可以这样玩,我们考虑一个长度为n的01序列,其中部分为1,部分为0。如果命令能保证对所有长度为n的序列正确排序,那么自然也能对我们的01序列进行排序。

现在我们断定如果所有可能的长度为n的二进制序列经过排序后都是有序的,那么一定能正确排序所有的长度为n的序列。用反证法,假设存在一个序列a不能被正确排序,那么考虑在对a应用m个命令后得到的新序列为b,考虑最小的数x,满足存在某个更小的数在b中排在x后面。接下来,我们将a中所有大于等于x的数改写为1,而所有小于x的数改写为0,那么经过m个命令后,得到的新序列实际上是将b中所有大于等于x的数改成1,而其余数改写为0,这样我们就发现一个长度为n的二进制序列不能被正确排序,这与前提相悖。

用这种方式做校验,时间复杂度为O(mn2n)

提供一道题目:SRM761 SortArray。

问题2:给定长度nm个命令,判断是否对于所有长度为n的序列,执行上述m个命令后都能得到有序序列。命令从前往后执行,每个命令指定若干个位置,将这些位置的数从小到大进行重排。其中n60m10

提供一道题目

题目1的做法在这里不适用,但是我们可以借鉴它通过对所有二进制串排序的校验方式。

如果n=1,很显然所有指定都是能保证正确排序的。下面考虑n>1的情况。

N={1,2,,n}

记第i个命令指定的下标集合为Ii,记Si=ij=1Ij。很显然指令能正确重排的前提是Sm=N

我们考虑一个函数f:Si{0,1}。我们称函数fi可达的,当且仅当存在一个01序列b1,,bn,经过前i个命令处理后,对于jSi,有f(j)=bj

那么我们可以得到我们排序算法成立的充要条件,所有m可达的函数都是递增函数,而这样的函数最多只有n+1个。我们可以通过枚举所有可能的m可达函数来校验我们的排序算法是否有效。

我们记Di=SiSi1,令S0=。那么当我们执行完前i1个命令后,要处理第i个命令时,实际上真正会影响结果的只有输入二进制串Di位置上的1的数目,它只有|Di|+1种可能。

因此我们暴力枚举的时间复杂度为O(ni=m|Di|+1)。由于|D1|+1++|Dm|=n+m,因此有上界O((n+mm)m)

这里我们需要使用二进制来表示集合,以及利用它快速计算交集并集等操作,否则时间复杂度需要额外乘上一个n

基础排序算法

冒泡排序

要排序大小为N的数组A。循环下面流程直到整个数组有序

  • 顺序枚举i=0,1,,N2,如果Ai>Ai+1,则交换两个数。

很显然冒泡排序一定会结束(每次都至少减少一个逆序对),且最终数组一定有序。

下面分析一下冒泡排序的原理:

考虑另外一个大小为N的数组B,其中Bi表示A0,,Ai1中比Ai大的数的个数。很显然Bii。如果A是一个置换,不难发现AB有唯一对应关系(可以O(NlogN)的时间复杂度内相互转换。当A有序的时候,B的所有元素都是0

考虑一次冒泡排序的循环体,其本质上做的事情是:

  1. 对于i=0,,N1,将Bi修改为max(Bi1,0)
  2. 将数组B循环左移一步,即新的数组为B1,,BN1,B0

因此我们直接操作B的话,可以快速模拟出A经过k次冒泡后的排列。同时只需要经过maxB次冒泡整个数组有序。