全排列的Java实现

全排列是一个比较经典的问题,昨天被问到全排列算法,竟一时语塞,因为的确我的算法积累太单薄,研究得太少. 多关注这些问题,对分析问题解决问题还是很有帮助的.

废话不多说,进入正题吧. 首先还是写个虚基类,即便用不到呵呵~虚基类里照旧是一些必要方法和虚的permutate()方法.这个代码就不贴了,可以参见笔者另一篇文章.

递归解决这个问题是比较常见的,实际上递归的思想也是很不错的,虽然会占用较多空间.考虑两个数的全排列就是<1 2>或者<2 1>,三个数的全排列就是取尽这个数组的任意一个数a(a属于{Vn})与全排列剩下两个数,构成的排列...N个数就是取尽每一个数,然后全排列剩下N-1个数,构成的排列.

设定一个可比较值大小的数组{V(n)}.perm({V(n)})=V1#perm({V(n-1)}/V1)+V2#perm({V(n-1)}/V2)+...+Vn#perm({V(n-1)}/Vn). 其中perm({V(n-1)}/Vn)表示除了元素Vn以外的剩下的数组.

根据以上算法,笔者尝试用自己浅薄的思维来实现这个基本算法,

首先是数据的存储和表示问题.由于一开始就不打算用Collection,而就着简单的数组,而笔者不喜欢走回头路,所以就盯着数组不放了.数组,就面临着没办法自由删除节点,因为数组都是连续固定长度的存储空间.如何表示节点的删除是一个问题.

第二是多分支递归问题.因为递归分支是多达n个(n随着递归深度的增加而减少,初始递归的分支是n个),每个分支都需要本次过程所需的各自不同的原材料,原材料因此也需要n份,如果把初始数组复制n份是不是太浪费空间了呢?如果不这样做,那么第三个问题,即如何输出排列呢?

第三是排列如何输出的问题.这个问题又是和第一二个问题耦合的.因为数组时固定的,而多个分支递归传入参数的信息需要各不相同并且互不影响(这保证输出的正确性),如果每次都任取一个元素(即便是按规律取元素)立即打印输出, 那么多个分支都在交错打印输出,在控制台上人眼就毫无办法区分,相当于还是没有成功排列.

既然用了数组,所占空间固定无法移除,而恐怕又因为问题2而导致的原料复制问题,可想而知N个数就要复制N!次...这个代价是很不情愿看到的,笔者自己暂时没有想到好的解决.而采用一个伴随的布尔数组的想法,用布尔数组来表示数组中元素取出情况 布尔值本身所占空间很小,复制N!次对内存的占用仍旧能控制在一个可预见的范围.(题外话::Java解释器对布尔数组应该也会有优化,即声明一个布尔值可能占用了一个byte,但是new一个布尔数组所占用空间未必就达到n个byte::以上需要验证)

现在考虑解决交错输出问题,这个问题笔者仍然没有找到好的解决办法,而采用了一个和布尔数组类似的先存后输出方法,即这样的方法,所需要的String的个数仍然将达到N!个,这个事实上没办法避免了,因为N个数的全排列有N!个,那么在这个解决思路下,就需要N!个String来存储并最后打印输出,为了避免直接的String操作(那样更费空间),采用的是StringBuilder类.无论如何这也是较小范围的优化了,需要一个更好的算法实现.

代码如下,欢迎拍砖

@Override
public void permutate() {
boolean[] fetchInfo = InitBooleans();

doPerm(arrayEs, fetchInfo, arrayEs.length, new StringBuilder(""));

}


/**
* core permutation recursion function
* @param aEs the array to permulate
* @param FetchedInfo the info of fetch or not
* @param restnum indicates how many elements remain untouched.
* @param trace the result tracing String builder
*/
private void doPerm(E[] aEs, boolean[] FetchedInfo, int restnum, StringBuilder trace) {
if (restnum == 1) {
lastFetchAndPrint(aEs, FetchedInfo, trace);
return;
} else {
// 得到restnum个复制的fetchInfo[],两两不同
for (int i = 1; i <= restnum; i++) {
// 新建一个布尔数组,表达下一步迭代的,并决定被输出值(被取出值)
StringBuilder inheritedTrace = new StringBuilder(trace.toString());

boolean[] newFetched = stepDownBooleans(aEs, FetchedInfo, i,
restnum, inheritedTrace);

doPerm(aEs, newFetched, restnum - 1, inheritedTrace);

}
trace = null;
}
}


private boolean[] stepDownBooleans(E[] aEs, final boolean[] oldFetched,
final int whichStep, final int restnum, StringBuilder trace) {
if (whichStep > restnum || whichStep <= 0) {
System.err.println("algorithm fails.");
throw new IllegalArgumentException();
}
boolean[] newFetched = new boolean[oldFetched.length];
int count = 0;
for (int i = 0; i < newFetched.length; i++) {
if (oldFetched[i]) {// 如果满足不是false并且轮到某个true节点该他置为假,那就置之
count++;
if (count == whichStep) {
newFetched[i] = false;// 取出(置为假值)
trace.append(aEs[i] + " ");// 输出
} else {
newFetched[i] = oldFetched[i];// true
}
} else {// 否则简单复制(已有假值)下来即可
newFetched[i] = oldFetched[i];// false
}

}
return newFetched;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值