遞歸算法在排列問題中的應用

前言

近日看到一個很有趣味的編程題,激起了鄙人強烈的研究慾望。在解答該問題的同時,發現了採用遞歸來計算排列的方法,現將方法和程序代碼公佈,歡迎廣大算法愛好者前來閱讀和交流。

原問題

有2N(3<N<40)個木塊,並對之編號爲1-N,當然,同一編號需要在兩個木塊中出現。然後將這些木塊這樣排序,編號爲n的木塊間需要間隔n個位置。爲了便於把問題說清楚,舉例說明。例如當N=3時,將得到入下排序3、1、2、1、3、2;當N=4時,輸出4、1、3、1、2、4、3、2;當N=5時,沒有找到符合條件的排列。
請編寫程序能夠找出任意輸入4<N<40的一種可行排列。

編程思想

當我看到這個問題時,首先想到的解決方案是先把所有的排列找出來,存入集合。然後從集合中遍歷出所有排列,依次計算該排列是否符合要求。
如何找出一組數字的所有排列,是最棘手的問題。我的方法是先定義兩個數組A和B,A數組的下標用來表示第幾個位置,數組中元素的值表示該位置放置的數字是多少。B數組的下標表示這些數字,1、1、2、2…,數組中元素的值表示數字是否已經被放置。採用循環遍歷B數組,如果發現未被放置的數字就放置在A數組中,即將B數組中元素的下標賦值給A數組中元素,以此來模擬一次排列的過程。一次放置結束後,採用遞歸的方法,進入下一個位置後再次遍歷B數組,發現未被取出的數字則取出後放置到該位置中。直到把所有位置均放置入數字,不再進行遞歸,並收集排列存入集合。
待將所有排列收集進一個集合後,遍歷集合,依次取出數組。取出一個數組後,從1開始循環計算當前數字在數組中的兩個位置,再判斷兩個位置間隔是否等於數字本身,相等則符合條件。如果所有數字均符合條件,說明這個排列是我們需要的。

算法

1、定義一維數組position,數組的下標用來表示第幾個位置,數組中元素的值表示該位置放置的數字是多少。
2、定義二維數組array,數組的下標表示這些數字,1、1、2、2…,數組中元素的值表示數字是否已經被放置。
3、定義變量i,表示位置。
3、爲position數組賦初始值爲0,當position[i]=0時表示該位置尚未被放入數字。爲array數組賦初始值,當array[i][j]=0時,表示該數字尚未被放入位置中。
4、從第1個位置開始,在每個位置都要採用循環遍歷arry數組,找到未被放置的數字依次放置到該位置中。
5、找到未被放置的數字時將array數組中元素值設置爲0表示該數字已經被取出放置。
6、i自增1,表示進入下一個位置。
7、如果i的值等於數字的數量,說明放置完成,將得到的排列存入集合。否則通過遞歸的方式找出第下一個位置可以放置的數字進行放置。
8、收集完所有排列後,遍歷集合中的所有收集到的排列。
9、定義變量a表示某排列中位置符合條件的數字的數量(兩個數字的間隔等於數字本身這個條件)。
10、從1開始,直到輸入的數字爲止。求該數字在排列中的兩個位置,如果兩個位置間距等於數字本身,則說明該數字的位置符合要求,將a自增1。
11、如果上一步結束後a的數字等於最初輸入的數組number,說明該排列裏面所有的數字位置都符合要求,是符合條件的排列。
12、採用循環遍歷的方式展示符合條件的排列。

程序代碼

public class Arrange {	
	//定義集合,用來存儲排列
    List list=new ArrayList();	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println("請輸入一個數字");
		Scanner input = new Scanner(System.in);
		int number = input.nextInt();
	    //定義一維數組,數組的下標用來表示第幾個位置,數組中元素的值表示該位置放置的數字是多少
		int[] position=new int[number*2];
		//定義二維數組,數組的下標表示這些數字,1、1、2、2....,數組中元素的值表示數字是否已經被放置
		int[][] array = new int[number][2];
		//爲position數組賦初始值爲0,當position[i]=0時表示該位置尚未被放入數字
		for(int i=0;i<number*2;i++){
			position[i]=0;					
		}
		//爲array數組賦初始值,當array[i][j]=0時,表示該數字尚未被放入位置中
		for(int i=0;i<number;i++){
			array[i][0]=1;
			array[i][1]=1;			
		}	
		System.out.println("賦值完畢");
		
		Arrange arrange=new Arrange();
		
		//初始位置爲0
		int x=0;
		//調用capture方法,收集所有排列,capture方法爲本類中的核心方法,寫在本程序的下面
		arrange.capture(position, array, number,x);		
		System.out.println("已經收集所有排列,正在檢查哪些符合條件");
		
		//遍歷集合中的所有收集到的排列
		for(int m=0;m<arrange.list.size();m++){
			int[] result=(int[]) arrange.list.get(m);
			//定義變量a2表示某排列中符合條件的數字的數量(兩個數字的間隔等於數字本身這個條件)
			int a2=0;			
			//從1開始,求該數字在排列中的兩個位置
			for(int x1=1;x1<=number;x1++){
				//定義數組a1,用來存儲某數字在排列中的位置
				int[]a1=new int[2];
				int x3=0;
				//遍歷數組,找出某個數字在排列中的兩個位置
				for(int x2=0;x2<result.length;x2++){
					if(result[x2]==x1){	
						//找到位置,將位置數字存儲進a1數組
						a1[x3]=x2+1;						
						x3++;
					}
				}
				//求兩個位置中間的間隔
				int a=a1[1]-a1[0]-1;
				//如果間隔不等於數字本身,則說明部分和條件,結束本輪循環
				if(a!=x1){
					break;
				}else{
				//如果相等,說明該數字的位置符合要求,a2自增
					a2++;
				}								
			}
			//當a2==number,說明排列中的每個數字都符合要求,即找到了需要的排列
			if(a2==number){
				System.out.print("已經找到這樣的排列:");
				//展示程序計算的結果
				for(int i6=0;i6<result.length;i6++){
					System.out.print(result[i6]);
				}
				System.out.println();
			}					
		}		
	}
	
	public void capture(int[]position,int[][]array,int number,int i){
		//從第1個位置開始,在每個位置都要遍歷arry數組,找到未被放置的數字依次放置
		//這個for循環表示在第x個位置分別放1,2,3....
		for(int j=0;j<number;j++){
			//當數組元素的值爲初始值1時,表示這個數字未被放置
			if(array[j][0]==1){
				//定義一個新數組,將原數組的值全部賦給它,避免原數組值在遞歸中被改變影響接下來的循環
				int[]positionChild=position.clone();
				//將被放置數字的位置賦給positionChild數組中的元素
				positionChild[i]=j+1;
				//定義一個新數組,將原數組的值全部賦給它,避免原數組值在遞歸中被改變影響接下來的循環
				int[][]arrayChild=new int[number][2];			
				for(int i2=0;i2<array.length;i2++){
					arrayChild[i2][0]=array[i2][0];
					arrayChild[i2][1]=array[i2][1];
				}
				
				//arrayChild[j][0]值設置爲0表示該數字已經被取出放置
				arrayChild[j][0]=0;
				//接下來要進入下一個位置放置數字
				int i1=i+1;
				//如果位置的數已經等於number,說明數字放置完成
				if(i1==number*2){
					//將得到的排列存入集合
					list.add(positionChild);
				}else{
					//如果尚未放置全部數字,通過遞歸的方式確定第下一個位置可以放置的數字					
					this.capture(positionChild, arrayChild, number,i1);
				}			
			}
			
			if(array[j][1]==1){
				int[]positionChild=position.clone();
				positionChild[i]=j+1;			
				int i1=i+1;		
				int[][]arrayChild=new int[number][2];			
				for(int i2=0;i2<array.length;i2++){
					arrayChild[i2][0]=array[i2][0];
					arrayChild[i2][1]=array[i2][1];
				}			
				arrayChild[j][1]=0;
				if(i1==number*2){
					list.add(positionChild);
				}else{				
					this.capture(positionChild, arrayChild, number,i1);
				}	
	       }
			
		}
				
	}

}

總結

該問題的核心是如何一個程序計算一個數字組合(或其他組合)的所有排列,鄙人在解決此問題中想到的方法是採用循環加遞歸。從第1個位置開始,在每個位置中均使用循環來遍歷數字組合,若發現沒有被排序的數字則將依次其放入該位置中。每次放置後,檢查當前位置是否是最後一個位置。不是的話則進入下一個位置,採用遞歸的方法重新遍歷數字組合,若發現沒有被排序的數字則將依次其放入該位置中……直到當前位置是最後一個位置時,待數字放置結束後將排列存入集合。

下面的代碼採用遞歸的方法實現簡單的求一組數字的所有排列並展示:

public class SimpleArrange {
	List list=new ArrayList();

	public static void main(String[] args) {
		// TODO Auto-generated method stub		
		System.out.println("請輸入一個數字");
		Scanner input = new Scanner(System.in);
		int number = input.nextInt();
		//定義一個位置數組,數組的下標用來表示第幾個位置,數組中元素的值表示該位置放置的值是多少
		int[] position=new int[number];
		//定義一個數組,數組的下標表示這些數字,1、1、2、2....,數組中元素的值表示數字是否已經被放置
		int[]array = new int[number];
		//爲數組賦初始值,初始值1 表示位置尚未被放置進數字,數字尚未放置進某位置
		for(int i=0;i<number;i++){
			position[i]=1;
			array[i]=1;			
		}
		
		SimpleArrange A1=new SimpleArrange();
		//定義初始位置,因爲數組的下標從0開始,所有初始位置我也從0開始
		int i=0;
		//調用方法得到所有排列的集合,本方法是程序的核心,方法體見程序的下半部分
		A1.capture(position, position, number,i);
		
		//展示收集到的所有排列
		for(int m=0;m<A1.list.size();m++){
			int[] result=(int[]) A1.list.get(m);
			for(int n=0;n<result.length;n++){
				System.out.print(result[n]);
			}
			System.out.println("-------------");
		}

	}
	
	//先從檢查第一個位置開始,在第1個位置中遍歷數組。傳入的參數i表示位置,值爲1。
	public void capture(int[] position,int[]array,int number,int i){
		//遍歷存儲數字的數組,查看當前位置可以放置的數字有哪些,然後逐一放置	
		for(int j=0;j<number;j++){
			//如果數組的元素的值爲1,表示該元素表示的數字尚未被放置
			if(array[j]==1){
				//複製數組,避免遞歸中改變原數組的值影響下一次循環
				int[]positionChild=position.clone();
				int[]arrayChild=array.clone();
				//在當前位置上放置數字
				positionChild[i]=j+1;
				//表示該數字已經被取出							
				arrayChild[j]=0;
				//進入下一個位置
				int i1=i+1;	
				//如果位置的數值是number,說明位置已被填滿
				if(i1==number){
					//得到的排列存入集合
					list.add(positionChild);
				}else{
					//通過遞歸的方式確定第下一個位置可以放置的數字
					this.capture(positionChild, arrayChild, number,i1);
				}							
			}			
		}		
	}
}

用遞歸計算數學排列題示例

我從網上找了一個數學上的排列題(高中數學),相信高中數學成績好的同學都會用數學的方法來做。下面我採用遞歸算法來編寫程序計算排列題。

題目:有6道選擇題,答案分別爲A 、B 、C 、D 、D 、D ,在安排題目順序時,要求三道選D 的題目任意兩道不能相鄰,則不同的排序方法有哪些?

這道題編程的話基本步驟和方法和我以上寫的兩個程序相同,但是因爲有限制條件,所以需要加入判斷語句來得出符合限定條件的結果即可。

步驟和程序代碼:

public class ArrangeQuestion {
	List list=new ArrayList();

	public static void main(String[] args) {
		// TODO Auto-generated method stub		
		
		int number = 6;
		//定義一個位置數組,數組的下標用來表示第幾個位置,數組中元素的值表示該位置放置的值是多少
		char[] position=new char[number];
		//定義一個數組,表示需要排序的字母
		char[]array = {'A','B','C','D','D','D'};	
		ArrangeQuestion A1=new ArrangeQuestion();
		//定義初始位置,因爲數組的下標從0開始,所有初始位置我也從0開始
		int i=0;
		//調用方法得到所有排列的集合,本方法是程序的核心,方法體見程序的下半部分
		A1.capture(position, array, number,i);
		
		//展示收集到的所有排列
		for(int m=0;m<A1.list.size();m++){
			char[] result=(char[]) A1.list.get(m);
			for(int n=0;n<result.length;n++){
				System.out.print(result[n]);
			}
			System.out.println("-------------");
		}

	}
	
	//先從檢查第一個位置開始,在第1個位置中遍歷數組。傳入的參數i表示位置,值爲1。
	public void capture(char[] position,char[]array,int number,int i){
		//遍歷存儲數字的數組,查看當前位置可以放置的數字有哪些,然後逐一放置	
		for(int j=0;j<number;j++){
			//如果數組的元素不爲X,表示該元素表示的數字尚未被放置
			if(array[j]!='X'){
				//複製數組,避免遞歸中改變原數組的值影響下一次循環
				char[]positionChild=position.clone();
				char[]arrayChild=array.clone();
				//在當前位置上放置字母
				positionChild[i]=array[j];
				//表示該數字已經被取出							
				arrayChild[j]='X';
				//到第二爲位置時,需要判斷前後兩個位置的字母是否相同
				//注意數組的下標從0開始,所有1表示第二個位置
				if(i>0){
					if(positionChild[i-1]!=positionChild[i]){					
						//進入下一個位置
						int i1=i+1;	
						//如果位置的數值是number,說明位置已被填滿
						if(i1==number){
							//得到的排列存入集合
							list.add(positionChild);
						}else{
							//通過遞歸的方式確定第下一個位置可以放置的數字
							this.capture(positionChild, arrayChild, number,i1);
						}					
					}
				}else{
					int i1=i+1;	
					this.capture(positionChild, arrayChild, number,i1);
				}
									
			}			
		}		
	}
}

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