从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列。
公式:全排列数f(n)=n!(定义0!=1),如1,2,3三个元素的全排列为:
1,2,3
1,3,2
2,1,3
2,3,1
3,1,2
3,2,1
共321=6种。
第一种:交换法(没有字典序)
通过字符数组每个字符相互交换形成全排列,但是要注意在每次交换后要记得回溯,保证下一次的交换是原本的数组顺序才能得到新的排列顺序。
如上图所示,以最左边排列为例,在进行一次排列,因为交换,所以数组的值会变化到k=3时的对应排列,如红色箭头所示,如果想要进行下一次排列交换,必须恢复到原数组ABC的顺序,所以我们使用回溯即黑色箭头所示,将k=3的排列恢复到最一开始的ABC。
import java.util.*;
public class 全排列_无字典序{
//k代表交换到第几个字符
static void f(char a[],int k) {
//全部交换完毕打印
if (k==a.length) {
for (int i=0;i<a.length;i++) System.out.print(a[i]+" ");
System.out.println();
return ;
}
//从第k层开始进行交换
for (int i=k;i<a.length;i++) {
swap(a,k,i);
f(a,k+1);
swap(a,k,i);//必须回溯到数组最初始的状态
}
}
//交换函数
static void swap(char a[],int i,int j) {
char t=a[i];
a[i]=a[j];
a[j]=t;
}
public static void main(String args[]) {
Scanner in=new Scanner(System.in);
String s=in.next();
char a[]=s.toCharArray();
f(a,0);
}
}
**输入:**
ABC
**输出:**
A B C
A C B
B A C
B C A
C B A
C A B
//无去重
**输入:**
ABCC
**输出:**
A B C C
A B C C
A C B C
A C C B
A C C B
A C B C
B A C C
B A C C
B C A C
B C C A
B C C A
B C A C
C B A C
C B C A
C A B C
C A C B
C C A B
C C B A
C B C A
C B A C
C C B A
C C A B
C A C B
C A B C
这种方法可以适用于只要求求全排列,而不注重其顺序,因为我们可以观察出来因为交换的缘故,输出的第5和第6个排列其实没有按照字典序排列。
第二种:前缀法(有字典序)
交换法利用字符数字对每个字符进行交换完成全排列,而前缀法使用字符串,在不同的前缀上加上没有被用过的其他字符形成不同排列。
但是需要注意的时的是,如果使用前缀法,对有重复出现的字符比如ABCC,当C成为前缀后,他会认为C已经在前缀里,就不会把他加入前缀,于是我们要加判断条件,保证前缀里的已有字符必须小于原数组里的重复字符的数,也就是保证两个C都能进入排序。
import java.util.Scanner;
import java.util.*;
public class 全排列_有字典序 {
//全排列,a为原数组,s为前缀
static void f(char a[],String s) {
//当前缀和和原数组长度相同打印
if (s.length()==a.length) {
System.out.println(s);
return ;
}
//不重样的遍历增加至前缀里
for (int i=0;i<a.length;i++) {
char t=a[i];
/* 判断重复使用,切记保证 例如:原数组为ABCC,
* 就算前缀里已经有了C,但是只有一个
* 我们需要加入另一个,就判断他们之间的个数
* 如果进行这个特判,那将永远不会加入第二个C
* 递归永远结束不了*/
if (count(t,s)<count(t,a)) {
f(a,s+t);
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner in=new Scanner(System.in);
String s=in.next();
char a[]=s.toCharArray();
f(a,"");
}
static int count(char t,String s) {
int sum=0;
for (int i=0;i<s.length();i++) {
if (t==s.charAt(i)) sum++;
}
return sum;
}
static int count(char t,char s[]) {
int sum=0;
for (int i=0;i<s.length;i++) {
if (t==s[i]) sum++;
}
return sum;
}
}
**输入:**
AABC
**输出:**
AABC
AACB
ABAC
ABCA
ACAB
ACBA
BAAC
BACA
BCAA
CAAB
CABA
CBAA
至此,我们进行对比:
交换法: 简单,可以自动处理重复字符,但没有字典排序
前缀法: 复杂,需要手动处理重复字符,但是具备字典序
全排列_交换法_排序去重:
当我们碰到需要排序时又记不住前缀法,就可以使用简单的交换法加上TreeSet排序去重都安排上了。
import java.util.*;
public class 全排列_交换法_排序去重 {
static Set <String> set=new TreeSet <String> ();
//k代表交换到第几个字符
static void f(char a[],int k) {
//交换完毕后加入set进行排序去重
if (k==a.length) {
String s="";
for (int i=0;i<a.length;i++) {
s+=a[i]+"";
}
set.add(s);
return ;
}
//从第k层开始进行交换
for (int i=k;i<a.length;i++) {
swap(a,k,i);
f(a,k+1);
swap(a,i,k);//必须回溯到数组最初始的状态
}
}
//交换函数
static void swap(char a[],int i,int j) {
char t=a[i];
a[i]=a[j];
a[j]=t;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner in=new Scanner(System.in);
String s=in.nextLine();
char a[]=s.toCharArray();
f(a,0);
//遍历输出set全排列结果
Iterator it=set.iterator();
while (it.hasNext()) System.out.println(it.next());
}
}
**输入:**
ABCC
**输出:**
ABCC
ACBC
ACCB
BACC
BCAC
BCCA
CABC
CACB
CBAC
CBCA
CCAB
CCBA
只要排序不要排序也可以将交换法的结果全部收集后进行排序,都是可以的。
但是如果遇到要去除重复的排列,最后一种方法可以说是最简单又实用的啦。