knuth的SJT实现

在这里插入图片描述

思路

SJT算法就是给每个值一个方向,初始都向左(P1),然后从最大的值开始检查(P3),一直检查到最小的值,直到找到值,使得其方向上的下一个值小于它(P4),然后将其往那边移动一步(P5),然后继续从最大的值开始找(return to P2)。对于每个被检查的值,如果这个值的方向上的下一个值不小于它,就调转它的方向,然后继续检查次小的值。

这样为什么可以枚举到所有的排列呢?这实际上可以递归的证明。首先我们知道,n=1时,这样是可以做到枚举到所有排列的。然后我们假设对于n=N-1时,这样做可以做到枚举到所有的排列。然后我们要证明对于n=N时,这样做可以枚举到所有的排列。

首先,将N移动到最左边或者最右边之后,我们将1到(N-1)的子序列变换到下一个全排列,在算法中体现为继续检查次小值能不能移动。由假设可知,通过这种方式,子序列可以遍历到所有的全排列,所以我们只需要将N反向再穿插过去,就可以遍历到包含这个子序列的1到N的全排列了。

P1

\(a_1\)\(a_n\)是序列,\(c_j\)\(j\)的右边小于\(j\)的数的个数,\(o_j\)\(j\)的方向,1表示向左,-1表示向右。刚开始,序列是\(1,2,3,4,...,n\),所以把\(c_j\)都初始化为0,\(o_j\)都初始化为1。

P2

不知道干啥的

P3

然后我们选定选择要移动的数字\(j\),一开始找最大的数字,也就是令\(j = n\)。令\(s\)表示\(j\)的左边比\(j\)大的数字的个数,那么\(j\)的下标就是\(j - c_j + s\),可以形象地理解为,从\(j\)的左边拿掉比\(j\)小的\(c_j\)个数字,然后再将\(s\)个比\(j\)大的数字插入到\(j\)的左边。

P4

因为我们的思路是先让大的走,而且不允许小的值把大的“挤走”,所以既然我们现在是要让\(j\)走,说明大于\(j\)的都走不动了,也就是说,比\(j\)大的值中,向右走的必然全部集中在最右边,向左走的必然全部集中在最左边。因此,\(j\)走的那个方向上如果有小于\(j\)的值,那么该方向上的下一个值必然是小于\(j\)的。

我们令\(q = c_j + o_j\),如果\(j\)能走得动的话,这个\(q\)实际上就是\(c_j\)的新值。

如果\(q<0\),说明\(j\)在向右走,且右边没有比它小的值了,也就是说\(j\)走不动了。这时就跳转到P7,改变\(j\)的方向,然后令\(j = j - 1\),即继续检查下一个值能不能走得动。

如果\(q = j\),说明\(j\)在向左走,且左边没有比\(j\)小的值了,也就是说\(j\)走不动了。这时跳转到P6,如果\(j=1\),说明所有值都走不动了,这时算法就结束了。否则,同样跳转到P7,改变\(j\)的方向,然后继续检查下一个值。但是不同的是,对于下一个值,左边多了一个比它大的值,所以\(s\)要加一。

如果\(0\leq q < j\),那么\(j\)走得动,就跳转到P5,让它跟下一个值交换,把\(c_j\)更新成\(q\),然后跳转到P2,继续从最大的数字开始检查能不能走得动。

posted @ 2024-09-28 14:12  寻找繁星  阅读(0)  评论(0编辑  收藏  举报