您的位置:首页 > 理论基础 > 数据结构算法

18 获取给定的序列的所有排列, 组合

2016-01-15 10:00 441 查看

前言

本博文部分图片, 思路来自于剑指offer 或者编程珠玑

问题描述





思路

这里有两个问题, 一个是求所有的字符的全排列, 一个是求所有字符的组合

对于问题一, 书上给了一种解法

思路 : 对于一个原始序列, 第一次交换第一个字符 和第一个字符以及后面的字符, 然后递归进入交换第二个字符阶段[同样是交换第二个字符 以及之后的字符], 这样直到交换完所有的字符之后, 打印出序列, 接着返回上一层递归, 交换回刚才的交换的两个字符, 进入下一个交换对象的交换

对于[a, b, c, d], 这样一个序列来说, 第一个字符存在4种交换可能, 第二个字符存在3种, … 最后共有 “4 * 3 * 2 * 1” 种交换序列

接下来一种思路是我实现的比较传统的思路 : 将备选字符放在一个容器, 从中抽取一个数据[不放回], 然后进行递归, 直到抽出的元素个数达到了我们的需求

同样 对于第一次抽取可能存在4种可能, 第二次抽取可能存在3种可能, … 最后共有 “4 * 3 * 2 * 1” 种序列

对于问题二

思路 : 我们建立一个抽取规约 : 在抽取了一个元素之后, 我们只能抽取这个元素之后的其他元素[相比于上面传统的思路 选择的余地主要是少了抽取的元素之前的元素]

示范一下 : 比如 “a, b, c”, 假设我们第一次抽取了”b”, 对于问题一的思路, 剩下的备选元素为”a, c”, 然而 对于问题二, 剩下的备选元素为”c”, 因为如果在抽取”b”前面的数据”a”的时候, 可能会造成与第一次抽取”a”的某个组合重叠, so 建立这个规约的目的就是为了防止各个组成元素相同的排列的重复

参考代码

/**
* file name : Test11CharCompletePermutation.java
* created at : 9:23:24 AM Jun 8, 2015
* created by 970655147
*/

package com.hx.test05;

public class Test11CharCompletePermutation {

// 1. 给定一个char[] 要求打印出全排列
// 2. 打印出所有的组合
public static void main(String[] args) {

char[] chars = new char[] {'a', 'b', 'c', 'd' };
Deque<Character> deque = new LinkedList<Character>();
charCompletePermutation(chars, deque, chars.length);
//      charCompletePermutation02(chars, 0);
Log.horizon();

deque.clear();
charCombination(chars, deque);

}

// 思路 : 使用一个栈 [或者一个数组 [如果使用数组的话, 直接在下面push的地方改成响应的索引赋值, 然后去掉pop即可] ]来保存排列
// 每次从chars中拿走一个字符, 然后把剩下的字符传入charCompletePermutation 进行递归
public static void charCompletePermutation(char[] chars, Deque<Character> deque, int getN) {
if(chars == null) {
return ;
}
if(getN == 0 ) {
Log.log(deque);
}

for(int i=0; i<chars.length; i++) {
deque.push(chars[i]);
charCompletePermutation(copyExcept(chars, i), deque, getN-1);
deque.pop();
}

}
// 交换start 和start之后的数据
// 递归交换start+1 和start+1之后的数据
// 第一位有chars.length中可能, 第二位有chars.length-1种可能, ...
public static void charCompletePermutation02(char[] chars, int start) {
if(start == chars.length) {
Log.log(chars);
return ;
}

for(int i=start; i<chars.length; i++) {
swap(chars, start, i);
charCompletePermutation02(chars, start+1);
swap(chars, i, start);
}
}

// 计算字符序列的全组合
// 从getN属于[1, chars.length]  获取chars中getN个字符的组合
// 获取小于(chars.length/2)个数的组合的时候  使用charCombinationForN方法
// 获取大于(chars.length/2)个数的组合的时候  使用charCombinationForHalfLater方法
public static void charCombination(char[] chars, Deque<Character> deque) {

for(int i=1; i<=chars.length/ 2; i++ ) {
charCombinationForN(chars, deque, i);
}
for(int i=chars.length/2+1; i<=chars.length; i++) {
charCombinationForHalfLater(chars, chars, deque, chars.length-i);
}

}

// 适用于计算sourceChars中的长度小于(sourceChars.length/2)的组合
// 计算从chars中取出getN个字符的序列
// 从chars中取出一个字符, 然后递归在其之后的数据中取出getN-1个字符
private static void charCombinationForN(char[] chars, Deque<Character> deque, int getN) {
if(getN > chars.length ) {
return ;
}
// 注意 : 这里应该是退出条件之一, 使用完序列之后   应该return
if(getN == 0) {
Log.log(deque);
return ;
}

// .. 最多到(chars.length-getN)开始
int end = chars.length - getN;
for(int i=0; i<=end; i++) {
deque.push(chars[i]);
charCombinationForN(copyAfter(chars, i), deque, getN-1);
deque.pop();
}

}

// 适用于计算sourceChars中的长度大于(sourceChars.length/2)的组合
// 计算从chars中取出(chars.length-compeltementyN)个字符的序列
// 从chars中取出一个字符, 然后递归在其之后的数据中取出(compeltementyN-1)个字符
private static void charCombinationForHalfLater(char[] sourceChars, char[] chars, Deque<Character> deque, int compeltementyN) {
if(compeltementyN > chars.length ) {
return ;
}
// 注意 : 这里应该是退出条件之一, 使用完序列之后   应该return
if(compeltementyN == 0) {
//          Log.log(deque);
Log.logWithoutLn("[");
for(char ch : sourceChars) {
if(! deque.contains(ch)) {
Log.logWithoutLn(ch + ", ");
}
}
Log.logWithoutLn("]");
Log.enter();

return ;
}

// .. 最多到(chars.length-getN)开始
int end = chars.length - compeltementyN;
for(int i=0; i<=end; i++) {
deque.push(chars[i]);
charCombinationForHalfLater(sourceChars, copyAfter(chars, i), deque, compeltementyN-1);
deque.pop();
}

}

// 复制除了exceptIdx 之外的其他元素
private static char[] copyExcept(char[] chars, int excpetIdx) {
if((excpetIdx < 0) || (excpetIdx >= chars.length) ) {
return chars;
}

char[] newChars = new char[chars.length-1];
System.arraycopy(chars, 0, newChars, 0, excpetIdx);
System.arraycopy(chars, excpetIdx+1, newChars, excpetIdx, chars.length-excpetIdx-1);
return newChars;
}

// 注意 : 不包括from
// 复制chars中from之后的元素
private static char[] copyAfter(char[] chars, int from) {
if((from < 0) || (from >= chars.length) ) {
return chars;
}

char[] newChars = new char[chars.length-from-1];
System.arraycopy(chars, from+1, newChars, 0, newChars.length);
return newChars;
}

// 交换数组中给定的两个索引的数据
private static void swap(char[] arr, int idx01, int idx02) {
char tmp = arr[idx01];
arr[idx01] = arr[idx02];
arr[idx02] = tmp;
}

}


效果截图



总结

对于后两者的思路, 更好是使用一个boolean[] 来标记各个索引对应的数据是否可抽取, 但是 这里为了易读, 就这样copyExcept/ copyAfter了

注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 数据结构 java