如 abc 的全排列: abc, acb, bca, dac, cab, cba
一、 递归版本
1、算法简述
简单地说:就是第一个数分别以后面的数进行交换
E.g:E = (a , b , c),则 prem(E)= a.perm(b,c)+ b.perm(a,c)+ c.perm(a,b)
然后a.perm(b,c)= ab.perm(c)+ ac.perm(b)= abc + acb.依次递归进行
public static ArrayList allSort2(String str, int pos) {
ArrayList list = new ArrayList();
if(pos==0){
// str=quickSort(str);
}
int len = str.length();
if (len == 0) {
list.add("");
return list;
}
list.add(str);
if (len == 1) {
return list;
}
for (int i = pos; i < len; i++) {
str = swap(str, i, pos);
list.add(str);
if (pos < len - 1) {
list.addAll(allSort2(str, pos + 1));
} else {
return list;
}
}
return list;
}
二、 非递归版本
1、算法简述
要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。
如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",然后再将替换点后的字符串"6220"颠倒即得到"950226"。
如果达到这个数的最大,比如1234-à4321,这个时候就结束整个循环。
如果输入是一个非最小数,如1324,则将它转换为最小数,如1234,再进行排序。排序算法用快排,可以自己写一个,如果快排不会的话,就先看会再来接着看,或者自己想一个靠谱的算法,也可以直接用VC库中的qsort(s , n , sizeof(s[0]) , cmp);各参数是什么意思就自己在下面多花点时间吧。
public String[] allSort1(String str) {
int len = str.length();
if (len == 0) {
return null;
}
int count = 0, n = 1;
for (int i = 1; i <= len; i++) {
n *= i;
}
String[] strings = new String[n];
String mtuxString = str;
if (len == 1) {
strings[0] = mtuxString;
}
// while (!str.equals(mtuxString)) {
// mtuxString = swap(mtuxString, n, --n);
// }
for (int i = 0; i < n / len; i++) {
if (i % 2 == 0) {
for (int j = len - 1; j > 0; j--) {
mtuxString = swap(mtuxString, j, j - 1);
strings[count++] = mtuxString;
if (j == 1) {
mtuxString = swap(mtuxString, len - 1, len - 2);
strings[count++] = mtuxString;
}
}
} else {
for (int j = 1; j <= len - 1; j++) {
mtuxString = swap(mtuxString, j, j - 1);
strings[count++] = mtuxString;
if (j == len - 1) {
mtuxString = swap(mtuxString, 0, 1);
strings[count++] = mtuxString;
}
}
}
}
return strings;
}
三、非递归还有一种方法
描述:和上一种不同的是:这种算法比较笨,但很好理解,不用按照上一种那么严格从小到大进行排列输出。
首先先将最后一个数从右往左依次交换输出,然后判断个数是否为基数,交换离该数最远端的两个数,再把第一个数从左往右交换输出,交换远端的两个数,如此进行循环就能排列完全部的数。这说得可能比较抽象,看一个例子:
E.g: 1 2 3 4
第一次:(从右往左):1 2 4 3 --- 1 2 4 3 --- 1 4 2 3 --- 4 1 2 3 把最后一个数依次往前移
交换:2 和 3 ---> 4 1 3 2
第二次:(从左往右):4 1 3 2 --- 1 4 3 2 --- 1 3 4 2 --- 1 3 2 4 把第一个数依次往后移
交换:1 和 3 ----> 3 1 2 4 重复第一次,知道把所有数输出为止
这个具体就不实现了,仅仅把上面两种方法需要用到的swap方法和快速排序quickSort方法写出。
public static String swap(String str, int i, int j) {
if (str.length() == 0) {
return null;
}
String afterSwap = "";
char c;
char[] cs = str.toCharArray();
c = cs[i];
cs[i] = cs[j];
cs[j] = c;
afterSwap = String.valueOf(cs);
return afterSwap;
}
/**
* Qucik sort 54,12,9,89,3,19,29,1 (1)选择54作为中轴 并定义tmp=54 1,12,9,89,3,19,29,1
* (2) 定义一个队尾游标,从后向前扫描,选择小于54的第一个元素,并把它复制到54这个位置 1,12,9,89,3,19,29,89
* (3)定义一个队首游标,从前向后扫描,扫描到大于54的第一个元素,并把它复制到1这个位置 1,12,9,29,3,19,29,89
* (4)继续步骤2的扫描,扫描小于54的第一个元素,并把它复制到89这个位置 1,12,9,29,3,19,54,89
* (5)继续步骤3的扫描,当扫描游标触碰到队尾游标时还未发现大于54的元素,则把tmp复制到队尾游标所指向的位置
* (1,12,9,29,3,19)54(89) (6)分别对54左右两个数组执行步骤1
*
* @param str
* @return
*/
public static String quickSort(String str) {
int len = str.length();
if (len == 0) {
return "";
}
if (len == 1) {
return str;
}
String resultStr = "";
char[] cr = str.toCharArray();
char pivol = cr[0];
int i = 0, j = len - 1;
while (true) {
if (i < j) {
while (j >= 0 && j > i) {
if (cr[j] > pivol) {
j--;
} else {
cr[i] = cr[j];
break;
}
}
while (i < len && j > i) {
if (cr[i] <= pivol) {
i++;
} else {
cr[j] = cr[i];
break;
}
}
} else {
cr[j] = pivol;
resultStr = String.valueOf(cr);
break;
}
}
return quickSort(resultStr.substring(0, j)) + pivol
+ quickSort(resultStr.substring(j + 1, len));
}
四、 总结
至此我们已经运用了递归与非递归的方法解决了全排列问题,总结一下就是:
1.全排列就是从第一个数字起每个数分别与它后面的数字交换。
2.去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。
3.全排列的非递归就是由后向前找替换数和替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。