排列B

问候,

上周,我们谈到了生成排列的问题,我告诉你

这周将是组合。 不对; 还有更多要说的

关于排列,这就是本周技巧。 也许以后

我们将讨论一些组合(如果有兴趣的话)。

我们上周实现的小实用程序类能够生成一个

给定当前排列的下一个排列(如果有)。 但是如果我们想要

直接生成第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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值