您的位置:首页 > 其它

算法-全排列问题

2014-12-22 13:43 176 查看
背景-有一个字符串数组abcd,求这是个字母的所有排列.
有数学知识可知全排列的结果为n!
那么用程序如何列出这些排列呢?
1.递归
我们知道手工排列的话,肯定是按照顺序一步一变的.
我们用(a,b)表示ab的全排列
那么(a,b)=a(b)+b(a);
同理:
(a,b,c)=a(b,c)+b(a,c)+c(a,b)
于是递归思想就出现了,具体参看下列算法(java版):

static int pc=0;//统计总数
public static void permutation(char[] chs,int low,int high){
int i,j;
//枚举完毕
if(low==high){pc++;
ombination(chs,9,value);
/*for(i=0;i<high;i++){
System.out.print(chs[i]);
}
System.out.println();*/
}else{
//提取前缀,递归子序列
for(j=low;j<=high;j++){
swap(chs,low,j);
permutation(chs,low+1,high);
swap(chs,low,j);
}
}
}
public static void swap(char[] chs,int i,int j){
char tmp=chs[i];
chs[i]=chs[j];
chs[j]=tmp;
}


----------------------
本算法有一个很巧妙的做法就是,将下一个前缀提到序列的首部,不参与递归.完成后再还原,提取下一个前缀.
递归的方法虽然很好理解,但是在序列很长时非常耗时,因为额外需要使用栈.
下面介绍非递归的方法.
=======================================
2.
我先描述可以重复的情形,最后去掉重复就成全排列了,客官可要耐心一点.
引子:
考虑一种情况,有三个字符a b c ,要求这三个字符的有顺序的全部组合,可以重复
例如aaa,aab,aac ,...
很容易知道,每列均有3种字母可写,一共的组合是3*3*3=27种
用程序的迭代来看,一共有三列,每列有三种情形.把每列看做一层大循环,每个大循环内部有三个小循环.

推论:已知字符序列长度为len,那么一共有len层大循环和len层小循环.

我们用字符数组chs表示原始序列abcdefg...一共是len个字符.
对于每一层我们不可能都列出来,所以用一个数组表示:
line[len] :每一个元素表示一列,其值表示这一列进行到了第几次循环,同时它还表示这一列的取哪个字符(每一次小循环对应取一个不同的字符).
对应: line中的每一个元素对应一个大循环,它的值对应大循环内部的第几次小循环.

啊!你别说了!我晕了!!!!好吧,我们手工写一下,更直观
字符序列 对应数组值
aaa line[0]=0,line[1]=0,line[2]=0
aab line[0]=0,line[1]=0,line[2]=1
aac line[0]=0,line[1]=0,line[2]=2
aba line[0]=0,line[1]=1,line[2]=0
abb line[0]=0,line[1]=1,line[2]=1
abc line[0]=0,line[1]=1,line[2]=2

看懂了没?好了,如何表示循环层数理解了,那么如何控制它们循环呢?
很简单的问题,我们用一个循环控制整个程序的流程,每循环一次后,当前列的值+1,当值>=len时,就要退出当前列的小循环了.和for循环是一样的.例如line[2]=3时,line[1]必须+1,同时line[2]要置0
这里需要用一个变量表示正在执行大循环的是第几列.就用currentLine表示吧
问题,什么时候程序该结束了??
当然是第一列的循环执行完毕了,即line[0]>len-1
好了,废话多,搞个程序运行一下呗!
static int num=0;//统计组合数
public static void permutation(char[] chs,int n){
//用一个临时字符数组保存组合
char[] tmp=new char
;
int[] line=new int
;
int len=n;
int currentLine=0;
int pos=0;//当前列要取那个值
while(true){
//结束条件
if(currentLine==0&&line[currentLine]==len)break;
pos=line[currentLine];
if(currentLine<len-1){
//继续循环
if(pos<len){
//保存值
tmp[currentLine]=chs[pos];
//进行当前列的下一次循环
line[currentLine]++;
//循环下一列
currentLine++;
}else{
//该返回了
line[currentLine]=0;
currentLine--;
}
//循环完了,返回
}else{
//到了最后一列,每次小循环都是一种组合
if(pos<len){
tmp[currentLine]=chs[pos];
line[currentLine]++;
//注意这里,没有下一列了****
//打印出组合
for(int i=0;i<len;i++){
System.out.print(tmp[i]);
}
System.out.println();
num++;
}else{
//该返回了
line[currentLine]=0;
currentLine--;
}
}
}
}


char[] chs=new char[]{'1','2','3','4','5'};
运行permutation(chs,3);
num=27;
-------------------------
看了这么多,没解决实际问题啊!不允许重复怎么办??
只要理解了上面的方法,这个问题就很简单了.
首先:为什么会重复??
因为前面有一列已经取走了pos的值,当前列还会取.检查一下当前pos值有没有被取过不就行了
代码:
//假设没有取过
find=false;
for(int i=0;i<len;i++){
if(pos+1==line[len]){
find=true;
break;
}
}
只要取过,就进行下一次循环,否则就赋值.下面的最终的代码.用一个变量repeat表示是否考虑重复问题
static int num=0;//统计组合数
public static void permutation(char[] chs,int n,boolean repeat){
//用一个临时字符数组保存组合
char[] tmp=new char
;
int[] line=new int
;
int len=n;
int currentLine=0;
int pos=0;//当前列要取那个值
boolean find;
while(true){
//结束条件
if(currentLine==0&&line[currentLine]==len)break;
pos=line[currentLine];
if(currentLine<len-1){
//继续循环
if(pos<len){
//检查重复
find=false;
if(repeat){
for(int i=0;i<len;i++){
if(pos+1==line[i]){
find=true;
break;
}
}
}
if(find){
//进行当前列的下一次循环
line[currentLine]++;
continue;
}
//保存值
tmp[currentLine]=chs[pos];
//进行当前列的下一次循环
line[currentLine]++;
//循环下一列
currentLine++;
}else{
//该返回了
line[currentLine]=0;
currentLine--;
}
//循环完了,返回
}else{
//到了最后一列,每次小循环都是一种组合
if(pos<len){
//检查重复
find=false;
if(repeat){
for(int i=0;i<len;i++){
if(pos+1==line[i]){
find=true;
break;
}
}
}
if(find){
//进行当前列的下一次循环
line[currentLine]++;
continue;
}
tmp[currentLine]=chs[pos];
line[currentLine]++;
//注意这里,没有下一列了****
//打印出组合
for(int i=0;i<len;i++){
System.out.print(tmp[i]);
}
System.out.println();
num++;
}else{
//该返回了
line[currentLine]=0;
currentLine--;
}
}
}
}


char[] chs=new char[]{'1','2','3','4','5'};

运行permutation(chs,3,true);

num=6;

-------------------------

运行方式:
public static void main(String[] args) {
char[] chs=new char[]{'1','2','3','4','5'};
//递归法
//permutation(chs,0,chs.length-1);
//考虑重复
permutation(chs,5,true);
//不考虑重复
//permutation(chs,5,false);
System.out.println(num);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: