字符串的全排列、可重复排列、全组合(Java实现)

一、字符串的全排列

/**
 * Created by april on 2018/8/2.
 * 输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
 * 输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
 */
public class String_PermutationList
{
    /*
    * 字典序法
    * 123456789,最后一个是987654321
    *
    * 思想:从右到左若都是增的,也就没有下一个,否则找出第一次出现下降的位置
    * 举例:
    * 如何得到346987521的下一个
    1,从尾部往前找第一个P(i-1) < P(i)的位置
            3 4 6 <- 9 <- 8 <- 7 <- 5 <- 2 <- 1
        最终找到6是第一个变小的数字,记录下6的位置i-1
    2,从i位置往后找到最后一个大于6的数
            3 4 6 -> 9 -> 8 -> 7 5 2 1
        最终找到7的位置,记录位置为m
    3,交换位置i-1和m的值
            3 4 7 9 8 6 5 2 1
    4,倒序i位置后的所有数据
            3 4 7 1 2 5 6 8 9
    则347125689为346987521的下一个排列
     */
    public static ArrayList<String> PermutationList_1(String str){
        ArrayList<String> list = new ArrayList<String>();
        if(str==null || str.length()==0) return list;
        //将字符串转为char数组,因为要将数组进行排序,并且数组易查找值
        char[] chars = str.toCharArray();
        Arrays.sort(chars);
        //将char数组转为String添加进list,先输出第一个正序的
        list.add(String.valueOf(chars));
        int len = chars.length;
        while(true){
            int lessen_index = len-1;
            int largen_index;
            //从尾开始数过来第一个变小的字符
            while(lessen_index>=1&&chars[lessen_index]<=chars[lessen_index-1]){
                lessen_index--;
            }
            if(lessen_index==0) break;//
            largen_index=lessen_index;
            //求从lessen_index-1往后数的最后一个比它大的数
            while(largen_index<len && chars[largen_index]>chars[lessen_index-1]){
                largen_index++;
            }
            //交换lessen_index-1和largen_index
            swap(chars, lessen_index-1, largen_index-1);
            //从lessen_index开始到最后倒序置放
            reverse(chars,lessen_index);
            list.add(String.valueOf(chars));
        }
        return list;
    }
    public static void reverse(char[] chars,int lessen_index){
        if(chars==null || chars.length<=lessen_index) return;
        int len = chars.length;
        for(int i=0;i<(len-lessen_index)/2;i++){
            //第一个和最后一个开始交换,第二个和倒数第二个开始交换....
            int front = lessen_index+i;
            int end = len-1-i;
            if(front<=end) swap(chars, front,end);
        }
    }
    public static  void swap(char[] chars, int i, int j){
        char temp = chars[i];
        chars[i] = chars[j];
        chars[j] = temp;
    }
    /*递归求全排列,思想为回溯法

对于无重复值的情况
     *
     * 固定第一个字符,递归取得首位后面的各种字符串组合;
     * 再把第一个字符与后面每一个字符交换,并同样递归获得首位后面的字符串组合; *递归的出口,就是只剩一个字符的时候,递归的循环过程,就是从每个子串的第二个字符开始依次与第一个字符交换,然后继续处理子串。
     *
     * 假如有重复值呢?
     * *由于全排列就是从第一个数字起,每个数分别与它后面的数字交换,我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这两个数就不交换了。
     * 例如abb,第一个数与后面两个数交换得bab,bba。然后abb中第二个数和第三个数相同,就不用交换了。
     * 但是对bab,第二个数和第三个数不 同,则需要交换,得到bba。
     * 由于这里的bba和开始第一个数与第三个数交换的结果相同了,因此这个方法不行。
     *
     * 换种思维,对abb,第一个数a与第二个数b交换得到bab,然后考虑第一个数与第三个数交换,此时由于第三个数等于第二个数,
     * 所以第一个数就不再用与第三个数交换了。再考虑bab,它的第二个数与第三个数交换可以解决bba。此时全排列生成完毕!
    * */
    public ArrayList<String> PermutationList_2(String str){
        ArrayList<String> result = new ArrayList<>();
        if(str!=null && str.length()>0){
            PerPermutationList_2_helper(str.toCharArray(),0,result);
            Collections.sort(result);//排序
        }
        return result;
    }
    public  void PerPermutationList_2_helper(char[] chars,int i,ArrayList<String> list){
        //当为解空间的一个叶节点,找到一个解
        if(i == chars.length-1) list.add(String.valueOf(chars));
        else{
            Set<Character> charSet = new HashSet<Character>();
            for(int j = i;j<chars.length; ++j){
                if(j == i || charSet.contains(chars[j])){

                    charSet.add(chars[j]);
                    swap(chars,j,i);
                    PerPermutationList_2_helper(chars,i+1,list);
                    swap(chars,j,i);//复位
                }
            }
        }
    }
    public static void main(String[] args){
        String str = new String("abc");
        System.out.println(PermutationList_1(str));
    }
}

二、可重复排列

public class String_PermutationList_Duplicate
{
    /*可重复排列
    1、第一个字符可从abc中选择一个,三种选择
    2、abc组成长度为2的字符的情况
    3、循环递归可以求出所有的可能
    4、循环退出条件:当减到length==0,就输出字符串
    * */
    public static void per(char[] buf, String str,int length){
        char[] chs = str.toCharArray();
        if(length == 0){
            for(int i = buf.length-1; i>=0; i--){
                System.out.print(buf[i]);
            }
            System.out.println();
            return;
        }
        for (int i = 0;i<chs.length;i++){
            buf[length-1] = chs[i];
            per(buf,str,length-1);
        }
    }
    public static void main(String[] args){
        String str = new String("abc");
        per(new char[str.length()],str,str.length());
    }
}

三、全组合

public class String_Comb
{
    /*全组合,abc各个位是否选取,一共有2^n(0~2^3-1)
    * abc 111,用0表示选取,用1表示不选取
    * */
    public static void comb(String str){
        char[] chs = str.toCharArray();
        int combNum = 1 << chs.length;//组合的个数有2^n的长度
        int k;
        for(int i = 0; i<combNum; i++){
            for(int j = 0; j<chs.length; j++){
                k = 1<<j;
                if((k&i)!=0){//按位与运算,如果结果为1就输出当前位,结果为0不输出
                    System.out.print(chs[j]);
                }
            }
            System.out.println();
        }
    }
    public static void main(String[] args){
        String str = new String("abc");
        comb(str);
    }
}

猜你喜欢

转载自blog.csdn.net/languolan/article/details/81533257
今日推荐