问候,
上周,我们谈到了生成排列的问题,我告诉你
这周将是组合。 不对; 还有更多要说的
关于排列,这就是本周技巧。 也许以后
我们将讨论一些组合(如果有兴趣的话)。
我们上周实现的小实用程序类能够生成一个
给定当前排列的下一个排列(如果有)。 但是如果我们想要
直接生成第m个排列? 例如给定的列表
长度的六个排列三个,例如,第三排列是什么?
我们显然不想遍历整个列表,接下来再生成所有列表
排列,直到获得第m个排列。 对于第三个排列
不会太糟,但是想像一下我们想要1,000,000个排列
13个长度的符号字符串? 我当然没有耐心等待
在我的小笔记本电脑想出答案之前很久。
本技巧定义了用于唯一排列序列的算法,即
排列顺序中的所有字符都是唯一的。 这是一个例子:
0: ABC
1: ACB
2: BAC
3: BCA
4: CAB
5: CBA
第二列显示长度3的所有唯一排列以及
第一列显示排列的“等级”。 我想要一个算法,
给定排列的长度(在示例中== 3)和等级编号
(在示例中也== 3),将为我提供相当有效的“ BCA”,
最好在与n成正比的big-Oh时间。
如果您仔细查看上述排列中最左边的字符
您会注意到规律性:A,A,B,B,C,C。通常对
size n最左边的字符在n!/ n个连续排列中保持不变。
在我们的示例中,n == 3和n!/ n ==(n-1)! == 6/3 == 2! == 2,实际上
最左边的字符每两个连续的排列保持不变。
第一个(n-1)! 排列具有相同的主导特征,第二个
(n-1)! 排列具有相同的主导特征,依此类推。
因此,如果我们知道排列的等级(在示例中为3),我们将立即
知道最左边的东西:我们将等级除以(n-1)! 我们选择
3/2! ==第一个可用字符,其中A是第零,B是第一个
C是第二个字符(我们总是从零开始计数)。
我们可以重复这个技巧:我们现在的排名是3%2! ==剩下1个,另外2个
其余的排列剩下字符A和C。
对于长度为2的排列以及字符A和C,我们必须选择
此列表中的第一个(从零开始计数)字母:0:A和1:C
这看起来像是Voodoo的Kaballah,但有一个严格的规律性:
“ r”是我们要查找的排列的等级,然后是第一个字符
是可用的字符号r /(n-1)!。 接下来,我们正在寻找r%(n-1)!
排序并选择第二个字符,依此类推。
这看起来有点像递归定义,确实如此,但是我们可以解决
反复进行。 这是算法:
1: public static <T> List<T> permute(List<T> list, int r) {
2: int n= list.size();
3: int f= fac(n);
4: List<T> perm= new ArrayList<T>();
5: list= new ArrayList<T>(list);
6: for (list= new ArrayList<T>(list); n > 0; n--, r%= f) {
7: f/= n;
8: perm.add(list.remove(r/f));
}
9: return perm;
}
我加入了一些泛型,因为我想拥有所有类型T的置换。
在第1行中,我们将类型为T的可用“事物”的List <T>和等级传递给它
数字“ r”。
在第二行中,计算“ n”,即排列的长度等于
可用事物的长度T; 它们必须都是唯一的。
在第3行中,我们计算n! (有关方法的简单实现,请参见下文
“事实”。
在第4行中,我们构建了一个新的空列表,其中可以包含T类型的内容。
第5行有点技术性:算法需要更改“列表”
参数。 但是,如果List <T>无法更改怎么办? 算法规避
通过制作列表的可变副本。 现在,原始列表可以保留
相同,我们只更改该列表的本地副本。
第6行完成了很多工作:对于列表中的每个位置,我们都会计算
可用字符的等级,并减小可用大小
字符列表。 变量“ f”的值分别为n!,(n-1)!,... 1。
请注意,严格来说不需要变量“ n”,因为它总是
等于变量“列表”的长度。
第7行会更新'f'的值(请参见上文)。
第8行从可用事物T和的列表中删除了所需事物T和
将其附加到正在生成的排列中。
最后,第9行返回给出的排名和可用的生成排列
东西T.
下一个方法稍微练习一下上面的方法; 它定义了三个列表
字符串(事物T的实例化)并生成所有排列:
public static void main(String[] args) {
List<String> list= Arrays.asList("A", "B", "C");
for (int i= 0, n= 3, f= fac(n); i < f; i++)
System.out.println(i+": "+perm(list, i));
}
我本可以使用具有不同元素类型的列表:
List<Integer> list= Arrays.asList(1, 2, 3);
这是泛型乐趣和自动装箱的地方:我们只写了一个
小方法,它可以处理各种类型而无需更改或
添加任何东西。
注意Arrays.toList方法如何生成只读列表。 不是
很重要,因为我们的“置换”方法不会改变它,即它会建立一个本地
列表的可写副本并更改副本。
因此,现在我们不能仅根据先前的排列生成排列,而是
我们可以按任何特定顺序生成它们。 奖牌有一个缺点:
我们在本周的上一个技巧中开发的算法并不关心如何
排列顺序很大。 给定一个例如1,000,000的排列
可以计算下一个排列的事物。 但是该算法具有
一些严重的数值限制:13! 是最大的学院
存储在一个int中,因此最多可以计算13个事物的排列。
将所有int更改为long可以按顺序购买更多东西
并使用BigIntegers重做整个算法将解决大小
完全限制 我把它留给读者作为练习;-)
哦,在我忘记之前,这是'fac'方法的定义:
public static int fac(int n) {
int f= 1;
for (; n > 0; f*= n--);
return f;
}
下次见到我们讨论一些设计模式时,
参加进来。 一些程序员不知道何时应用它们及其设计
最终变得比他们应得的更糟; 另一方面,其他程序员使用
他们所有程序中的单一设计模式及其设计看起来有点
像装饰完整的圣诞树。 下周我们将讨论一些。
亲切的问候,
乔斯
From: https://bytes.com/topic/java/insights/632857-permutations-b