字典序法实现全排列

来源:互联网 发布:苹果手机网络设置 编辑:程序博客网 时间:2024/06/06 02:50

        参考:http://yangyou230.iteye.com/blog/1307884        http://ashaochangfu.blog.163.com/blog/static/10425173020115217189184/

        全排列的生成算法就是对于给定的字符集,用有效的方法将所有可能的全排列无重复无遗漏地枚举出来

        现在说如果有一个字符串,那么对于这个字符串中的所有单个的字符,这个字符串就是这些字符的一个排列喽!那么,我们现在的目标是把这个字符串中的字符的所有可能的排列列举出来。

        用字典序法得到全排列的思路大概是这样的:我们需要有一个初始的排列状态,对于这个排列,用字典序法转换就得到下一个排列。我们把初始状态设置为字符从小到大地排,不断地用字典序法得到它的下一个排列,直到最后一个排列为止,而最后一个排列就是字符从大到小地排喽。比如说5个数字的所有的排列中最前面的是12345,最后面的是54321。
        字典序法的描述如下:

        设P是1~n的一个全排列:p=p1p2......pn=p1p2......pj-1pjpj+1......pk-1pkpk+1......pn
      1)从排列的右端开始,找出第一个比右边字符小的字符的序号j(j从左端开始计算),即 j=max{i|pi<pi+1}
      2)在pj的右边的字符中,找出所有比pj大的字符中最小的字符pk,即
      k=max{i|pi>pj}(右边的字符从右至左是递增的,因此k是所有大于pj的字符中序号最大者)
      3)对换pj,pk
      4)再将pj+1......pk-1pkpk+1pn倒转得到排列p''=p1p2.....pj-1pjpn.....pk+1pkpk-1.....pj+1,这就是排列p的下一个下一个排列。
      例如839647521是数字1~9的一个排列。从它生成下一个排列的步骤如下:
      自右至左找出排列中第一个比右边数字小的数字4 839647521
      在该数字后的数字中找出比4大的数中最小的一个5 839647521
      将5与4交换 839657421
      将7421倒转 839651247
      所以839647521的下一个排列是839651247。
      839651247的下一个排列是839651274。

        代码如下:

import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.Hashtable;import java.util.List;public class Test {public static void main(String[] args) { long a=System.currentTimeMillis();         String s="1587469320";         char[] arr=s.toCharArray();         Arrays.sort(arr);         List<String> result=new ArrayList<String>();         result.add(String.valueOf(arr));        while(lowPos(arr)!=-1){        int i=lowPos(arr);            int j=minInMaxThanPos(arr, i);            swap(arr, i, j);            reverseAfterI(arr, i);            result.add(String.valueOf(arr));         }        System.out.println(result.size());        long b=System.currentTimeMillis();        System.out.println("用时:"+String.valueOf(b-a));       // for(String e:result)        //System.out.println(e);                 }//从排列的右端开始,找出第一个比它的右边的字符(紧挨着它的)小的字符的序号//比如在"839647521" 自右至左找出排列中第一个比右边数字小的数字是'4' ,返回它的下标4public static int lowPos(char[] arr){int i;for(i=arr.length-1;i>=1;i--)if(arr[i-1]<arr[i])break;return i-1;}//在下标index的右边的字符中,找出所有比arr[index]大的字符中的最小的字符的下标//比如在"839647521" index=4时,arr[4]='4',// 在'4'右边所有大于'4'的字符中的最小者是'5',返回'5'的下标6public static int minInMaxThanPos(char[] arr,int index){Hashtable< Character,Integer> ht=new Hashtable< Character,Integer>();ArrayList<Character> tmp =new ArrayList<Character>();int j=0;for(int i=index+1;i<arr.length;i++)if(arr[i]>arr[index]){  ht.put(arr[i],i);  tmp.add(arr[i]); }if( ht.isEmpty())return -1;Collections.sort(tmp);return  ht.get(tmp.get(0));}//交换public static void swap(char[] arr,int i,int j){char tmp=arr[i];arr[i]=arr[j];arr[j]=tmp;}// arr 在 index之后的元素反转//比如在"839647521" 在index=6之后的元素反转是:"839647512"public static void reverseAfterI(char[] arr,int index){int j=arr.length-1;//arr 在 index之后的元素的长度int len=arr.length-1-(index+1)+1;for(int i=index+1;i<=index+len/2;i++)swap(arr,i,j--);}}

        好家伙,10个数字的全排列在我这台破机子上运行了14 秒钟,总共有3628800 个。把 s 改成 “132” ,运行结果:

result.size()=6
用时:0
123
132
213
231
312
321