1. 程式人生 > >Java下實現無重字串的全排列(遞迴和回溯方法)

Java下實現無重字串的全排列(遞迴和回溯方法)

開發十年,就只剩下這套架構體系了! >>>   

給定一個無重複字元的字串陣列,實現它的全排列

一、採用遞迴實現全排列

思想是從第一個字元開始排,一直排到只有一個字元那麼就說明一個排列完成了。否則就交換當前第k個字元和第i個字元(這裡的第i個字元指的是k後面所有字元的其中一個,例如abcd,當a固定,對bcd排列,那就要將b和c、b和d依次交換),再對k+1到m進行排列。當返回成功後就再將第k個字元和第i個字元再交換回來。

遞迴的終止條件就是k=m

k 為起始下標,m為結束下標,下面的函式用於返回從k到m的全排列

list[]用於儲存待排列的字串

public static void perm(char list[], int k, int m) {
        if(k == m) {
            System.out.println(list);
        }
        else {
            for(int i = k; i <= m; i++) {
                //交換第k個字元和第i個字元,因為java是按值傳遞,所以我就沒用函式交換
                char temp;
                temp = list[k];
                list[k] = list [i];
                list[i] = temp;
//再對k後面的字串進行全排列
                perm(list, k+1, m);
            //排列完後再換回來繼續下一層迴圈
                temp = list[k];
                list[k] = list [i];
                list[i] = temp;
            }
        }
    }


二、採用回溯實現全排列

思想是深度優先搜尋,比如要找12345的全排列的一種排列12345

第一層,有五種選擇,1、2、3、4、5,五個元素都標記沒被使用過

第二層,1下面有四種2、3、4、5, 1被標記使用過

第三層,2下面有三種3、4、5, 1、2被標記使用過

第四層,3下面有兩種4、5, 1、2、3被標記使用過

第五層,4下面就只能排5了,1、2、3、4被標記使用過

這時就找到了一種排列12345,然後將5標記為沒被使用過,返回到上一層4,將4也標記為沒使用過,但5可以選,然後再選4,這樣就實現了回溯,就可以得到所有解了。

具體程式碼如下:

引數n表示第幾層,list是一個char型的陣列,存放了待全排列的字串

used[]是一個bool型的陣列,具體下標對應的真假值判斷那個下標的字元有沒有被使用過,vector[]是一個整型陣列,用於存放一個排列的解,amount用於計數,result函式用於將解向量輸出成排列。

public static void perm(char list[], int k, int m) {
        if(k == m) {
            System.out.println(list);
        }
        else {
            for(int i = k; i <= m; i++) {
                //交換
                char temp;
                temp = list[k];
                list[k] = list [i];
                list[i] = temp;
                //返回k之後的全排列
                perm(list, k+1, m);
                //交換回來
                temp = list[k];
                list[k] = list [i];
                list[i] = temp;
            }
        }
    }


完整的可以通過輸入字串(要保證無重複字元)來返回所有排列的測試程式碼如下:

import java.nio.file.attribute.AclEntryPermission;
import java.util.Arrays;
import java.util.Scanner;
 
public class Generator {
    static boolean used[];//存放字元是否被使用過
    static char list[];//存放字串
    static int vector[];//存放當前排列的解向量
    static int amount = 0;//用於計數
    public static void main(String[] args) {
        // 使用者輸入字串
        String str;
        Scanner input = new Scanner(System.in);
        str = input.next();
        list = str.toCharArray();
        used = new boolean[str.length()];
        vector = new int[str.length()];
        
        //perm(list, 0, str.length()-1); //用遞迴實現全排列
        Aperm(0);//用回溯實現全排列,從第0層開始
        
        
    }
    
    public static void perm(char list[], int k, int m) {
        if(k == m) {
            System.out.println(list);
        }
        else {
            for(int i = k; i <= m; i++) {
                //交換
                char temp;
                temp = list[k];
                list[k] = list [i];
                list[i] = temp;
                //返回k之後的全排列
                perm(list, k+1, m);
                //交換回來
                temp = list[k];
                list[k] = list [i];
                list[i] = temp;
            }
        }
    }
    
    public static void Aperm(int n) {
        //第0層有i.length種選擇
        for(int i = 0; i < list.length; i++) {
            if(!used[i]) {
                vector[n] = i;//記錄當前解
                used[i] = true;//當前元素被使用
                if(n!= list.length - 1)
                    Aperm(n+1);//若不是最後一層則返回下一層的
                else//否則就是最後一層,那麼就找到了一種排列,輸出
                    System.out.println(amount++ +": "+ result(vector));
                //很重要,返回到上一層就得取消標記
                used[i] = false;
            }
        }
    }
    
    public static String result(int vector[]) {
        String string = "";
        for(int i = 0; i < vector.length; i++) {
            string += list[vector[i]];
        }
        return string;
    }
 
}