遞歸解決全排列問題

遞歸是一個非常重要的解題方法和思路,我們在生活中很多地方都用到了遞歸概念。現有一字符串序列,要求我們對其進行全排列,例如“ab”的全排列爲“ab”和"ba",編寫程序解決問題。

在數學中全排列問題是一個非常常見的問題,在概率問題中經常出現,通常全排列都是用大寫字母A來表示。我們在數學中確實經常做到全排列的題,但是,我們很少讓寫出全排列,大部分問題都是讓我們求出對於一個序列,全排列的個數,有可能確實有讓寫全排列的,但是其階數最大基本上不超過3,因爲階數爲4時,全排列的個數已經是十位數了,所以我們很少體驗到寫高階全排列的過程,而做的基本上都是算排列的個數。現在我們舉個例子:求出序列abcd的全排列個數。非常簡單,答案是24,方法是4*3*2*1 = 24。但是,這個求解個數的方法,其實也是使用了遞歸思想,現在讓我們捋一捋這裏面的關係:“4”代表什麼?"4"代表第一位可以出現的字符的個數,當我們考慮第一位時,所有的四個字符都還沒有動用,因此他們四個均可以出現在第一位;那麼"3"又是什麼呢?它的意思是當第一位確定後,第二位可能出現的字符的個數;同理,“2”是前兩位確定後,第三位可能出現的字符的個數;最後,“1”是前三位確定後,最後一位出現的字符個數。這些數字相乘,就代表了全排列出現的所有可能。注意,這個結果出現的前提是字符中沒有重複元素,如果有的話就要另當別論了。

這裏體現的遞歸思想只要思考一下就可體會出來,首先我們在考慮第一位時,有四種可能,此時我們考慮的是長度爲4的序列的開頭元素,而當第一爲元素確定以後,我們去考慮第二位的元素,理所當然只有三個,這是因爲第一位已經佔用了一個,這是理所當然的。確實這種理解方式不算錯,但是我們也可以通過另一個角度去看,我們確定第一位後,便不再考慮第一位,將第二位當作一個長度爲三的序列的首位,我們從三個元素中去挑一個來作爲它的首位;而第三位使用同樣的思路去考慮,就是在一個長度爲2的序列中,從兩個元素中去挑一個作爲它的首位。它的遞歸思想就這樣出來了。

簡單來說,就是:階數爲四的全排列,我們可以化解成兩個基本問題,一個問題是從四個之中選出一個作爲它的首位;另一個問題是,選出來的這個元素後面只要接上一個三階的全排列,那麼這個元素爲首的所有排列就得出來了。因此我們只要不斷變換開頭元素,然後去求去掉這個元素的全排列,即可得到所有的全排列。解決這個問題的方法買就是遞歸,下面我用圖解來詳細說明。首先是基本思路的概述:

然後是我們得到其中一種排列的過程:

這樣,問題就基本上明瞭了,遞歸過程其實就是忽略開頭元素,去求解一個比自己當前階數少一階的序列的全排列,而這個子問題的全排列的解決也是一個同樣的過程,這樣就完全符合遞歸思想了。我們只要在每一個進程中,讓當前規模下序列的每一個元素做一遍開頭,然後進入遞歸調用即可解決問題。下面是代碼:

#include <stdio.h>
#define G 100

void permute(int start,int end,char list[]); //排列函數,start爲開始位置下標,end爲結束爲止下標
void move(int a,int b,char list[]);//位置交換函數,用於讓每個元素做開頭,改變元素在數組中的位置

int main(void){
	char my_list[G];
	int i = 0;
	printf("請輸入字符元素:");
	while((my_list[i]=getchar())!='\n')i++;
	my_list[i] = '\0';
	printf("排列情況:\n");
	permute(0,i-1,my_list);
} 
void permute(int start,int end,char list[]){
	int i;
	if(start == end){//當開始位置下標和結束位置下標相等時,意味着當前的數組中只有一個元素了
		printf("%s\n",list);//此時應該輸出
		return;
	}else{//否則的話就要進行遞歸調用
		for(i = start;i<=end;i++){//i用於便利當前規模的問題,讓每個元素當開頭
			move(start,i,list);//移動位置,做出移動操作
			permute(start+1,end,list);//進行遞歸調用,將開頭加一,代表不去考慮第一個元素,進一步去考慮剩下三個元素。其實就是去解決剩下三個元素的全排列
			move(start,i,list);//問題解決完之後,再移動回來
		}
	}
	
}
void move(int a,int b,char list[]){
	char temp;
	temp = list[a];
	list[a] = list[b];
	list[b] = temp;
	return;
}

 

 

輸入及運行結果:

其他測試可自行實驗。

以上問題解決的是序列元素中沒有重複元素時的情況,當序列元素中有重複情況時,需要進行一些修改,下面附上修改後的代碼,這個代碼可以解決當序列中有重複元素時的全排列問題。

#include <stdio.h>
#define G 100

void permute(int start,int end,char list[]); //排列函數,start爲開始位置下標,end爲結束爲止下標
void move(int a,int b,char list[]);//位置交換函數,用於讓每個元素做開頭,改變元素在數組中的位置
int find_no_same(char list[],int pre,int start); //用於查看是否有和自己一樣的 

int main(void){
	char my_list[G];
	int i = 0;
	printf("請輸入字符元素:");
	while((my_list[i]=getchar())!='\n')i++;
	my_list[i] = '\0';
	printf("排列情況:\n");
	permute(0,i-1,my_list);
} 
void permute(int start,int end,char list[]){
	int i;
	if(start == end){//當開始位置下標和結束位置下標相等時,意味着當前的數組中只有一個元素了
		printf("%s\n",list);//此時應該輸出
		return;
	}else{//否則的話就要進行遞歸調用
		for(i = start;i<=end;i++){//i用於便利當前規模的問題,讓每個元素當開頭
			if(find_no_same(list,i,start)){
				move(start,i,list);//移動位置,做出移動操作
				permute(start+1,end,list);//進行遞歸調用,將開頭加一,代表不去考慮第一個元素,進一步去考慮剩下三個元素。其實就是去解決剩下三個元素的全排列
				move(start,i,list);//問題解決完之後,再移動回來
			}
		}
	}
	
}
void move(int a,int b,char list[]){
	char temp;
	temp = list[a];
	list[a] = list[b];
	list[b] = temp;
	return;
}

int find_no_same(char list[],int pre,int start){
	int i = start;
	for(;i<pre;i++){
		if(list[pre]==list[i]){
			return 0;
		}
	}
	return 1;
} 

具體原理比較簡單,大家參考代碼既可以明白,下面是測試樣例與輸出:

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