生成排列(全排列)的兩種寫法

問題簡述:輸出任意各自然數(可不連續)所有不重複的排列,即全排列,要求所產生的任一數字序列中不允許出現重複的數字。

解決方法:1.交換法    2.數組訪問法,兩種方法都是由dfs回溯完成,也可以使用剪支。

文末將給出DFS的套路模板

一.交換法

  方法是先固定一個元素,然後將固定的數字與其他元素進行交換

見下圖

import java.util.Scanner;

public class 生成排列交換法 {
	static Scanner sc = new Scanner(System.in);
	static int ans = 0;
	static int n = sc.nextInt();
	static int pre[] = new int[n];
	public static void main(String[] args) {
		for(int i = 0 ; i < pre.length ; i++) {
			pre[i] = sc.nextInt();
		}
		dfs(0);//從第0層,指最上層爲0
                /*
                *此處的dfs的參數爲0,是指將最上層當作第0層,也可以改爲1,但對應dfs函數中的 
                *if(step == n)就要相應的改動                                       
                */
		System.out.println(ans);
	}
	public static void dfs(int step) {
		if(step == n) {
			ans++;
			print();
		}
		for(int i = step ; i < n; i++) {
                        //此循環體一定要重點理解,弄清原理主要就靠這循環體
			swap(i,step);//對應圖中的元素交換
			dfs(step+1);
			swap(step,i);
		}
		
	}
	public static void print() {
		for(int i = 0 ; i < n ; i++) {
			System.out.printf("%5d",pre[i]);
		}
		System.out.println();
	}
	public static void swap(int i, int j) {
		int temp;
		temp = pre[i];
		pre[i] = pre[j];
		pre[j] = temp;
	}
}

 注意在順序問題上,是屬於dfs情形,一直探尋到底,直到最後框裏只剩一個元素的時候,再折返,這就是循環體中swap(step,i)的意義,採用回溯。

二.數組訪問法

import java.util.Scanner;

public class 全排列_n個不連續數字全排列 {
		static Scanner sc = new Scanner(System.in);
		static int n = sc.nextInt();
		static int ans = 0;
		static int pre[] = new int [n+1];
		static int result[] = new int [n];
		static int vis[] = new int[n];
		public static void main(String[] args) {
			for(int i = 0 ; i < n ; i++) {
				pre[i] = sc.nextInt();
			}
			dfs(0);
			System.out.println(ans);
		}
		private static void dfs(int step) {
			if(step >= n) {
				ans++;
				print();
				return;
			}
			for(int i = 0 ; i < n ; i++) {
				if(vis[i] == 0) {
					vis[i] = 1;
					result[step] = pre[i];
					dfs(step+1);
					vis[i] = 0;
				} 
			}
		}
		private static void print() {
			for(int i = 0 ; i < n ; i++) {
				System.out.printf("%5d",result[i]);
			}
			System.out.println();
		}
}

採用vis數組來記錄數組的元素訪問情況,數組在初始默認情況下元素都是0(即爲未被訪問過)。

最好去仔細畫一遍dfs的圖,看看其原理,是什麼時候往回退的,一步步往回退的過程就是回溯,過程中將已經訪問過的元素重新賦值爲0。因爲你賦值爲0的一個重要作用,就是在你觸底,不能在繼續訪問新元素的時候,要往回退,往回退的過程中還要有另一條路會用到此元素,重點理解的地方,這個數字只存在與一個數組中,你訪問數字,即爲訪問數組的元素,而且是隻有一個數組,所以要一步步的回溯。例如:從1-4的過程中,當你選擇step1:1      ->     step2:2    ->     step3:3      ->       step4:4 。此時1234這4個數字都被訪問過,vis[i]都爲1,如果你要換一種排列,他們都不在訪問範圍內,所以第一步回溯,將step4中的4 vis[i]重新置爲0,然後發現除了4沒有其他數字選擇,那麼再次回溯,將step3的數字的也置爲0(不是數字置爲0,而是vis[]置爲0),此時就有了選擇,除了3以外,4也沒被訪問過,所以第二種方案,爲step3爲4,然後繼續dfs,step4爲3.(最後的結果 爲 :1243)。

三.關於dfs的方法模板如下,僅限於簡單初始的dfs

具體的模板完全可以參照上面兩個代碼塊,其實算比較標準的模板代碼了

public static void dfs()//參數用來表示狀態  

{  

    if(到達終點狀態,也稱出口)  

    {  
        ...//主要爲打印或者結束處理  
        return;  
    }  
    for(擴展方式)  //循環一層的所有情況
    {  
        if(擴展方式所達到狀態合法)  
        {  
            具體操作;//根據題意來添加
            標記;  
            dfs();  
            (還原標記);  //是否還原標記根據題意,如果加上(還原標記)就是 回溯法  
        }  
    }  

}  
public static void dfs()//參數用來表示狀態  

{  

    if(到達終點狀態,也稱出口)  

    {  
        ...//主要爲打印或者結束處理  
        return;  
    }  
    for(擴展方式)  //循環一層的所有情況
    {  
        if(擴展方式所達到狀態合法)  
        {  
            交換;  
            dfs();  
            (還原標記,一般爲交換回來)//是否還原標記根據題意,如果加上(還原標記)就是 回溯法  
        }  
    }  
}  

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章