算法---全排列

来源:互联网 发布:制作手持身份证软件 编辑:程序博客网 时间:2024/06/05 23:45

背景-有一个字符串数组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);

}

0 0
原创粉丝点击