算法-全排列问题

背景-有一个字符串数组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[n];
  int[] line=new int[n];
  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[n];
  int[] line=new int[n];
  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);
 }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值