1. 程式人生 > >全排列 遞迴(非字典序) 深搜(字典序)

全排列 遞迴(非字典序) 深搜(字典序)

全排列問題初探,不含重複元素情況的討論。

糊的題目:

【題目描述】

給定一個由不同的小寫字母組成的字串,輸出這個字串的所有全排列。

我們假設對於小寫字母有‘a’ <‘b’ < ... <‘y’<‘z’,而且給定的字串中的字母已經按照從小到大的順序排列。

【輸入】

只有一行,是一個由不同的小寫字母組成的字串,已知字串的長度在1到6之間。

【輸出】

輸出這個字串的所有排列方式,每行一個排列。要求字母序比較小的排列在前面。字母序如下定

義:

已知S=s1s2...sk,T=t1t2...tkS=s1s2...sk,T=t1t2...tk,則S<T等價於,存在p(1≤p≤k),使得s1=t1,s2=t2,...,sp−1=tp−1,sp<tps1=t1,s2=t2,...,sp−1=tp−1,sp<tp成立。

【輸入樣例】

abc

【輸出樣例】

abc
acb
bac
bca
cab
cba

看了帖子也看了書,最後把當時的程式碼都糊上來了,因為懶就直接ctrl+/把其他的寫法個註釋掉了。需要的話,可以全部copy下來斟酌一番,考慮區別與不同,會有不小收穫。

按字典序的程式碼實現這裡用的是深搜,每次呼叫後從0開始,先後搜尋原序列,要麼賦值要麼記錄下標,最後到臨界條件一併輸出。

而不能字典序的遞歸回溯寫法,遞迴的函式f(n)的意義是——輸出第n+1位即其後的所有全排列序。是通過for迴圈對原序列每個位子上的所有情況與後面原素交換位置來實現的,其中通過標記來記錄是否選擇過,且由於交換的規則是與該次呼叫中for迴圈裡的a[i]交換,每次換位呼叫後需恢復,並進行下一次換位。

需注意的是得思考為什麼這樣無法實現每次輸出都按照字典序,由於交換程式碼的實現,拿三個以元素abc為原序列為例,當第一個位子的元素於三個位子元素交換後(也就是當前n == 0, 第二個for迴圈裡的i等於2時)就直接往後呼叫f(0+1),而這一次的輸出效果就是cba,而不是我們想要的先輸出cab。其中本質的原因在於f(n)函式中每次n與跨k(k>=1)個元素交換位置時(迴圈到i == n+k時),直接就向後遞迴,然後第一次交換後直接就以一種情況先輸出,再來回溯,繼續判斷符合條件輸出。這樣輸出的順序就會與字典需不相符。(換個角度其實可以看出其整體的交換輸出方式,就像一個輸出模板,內部數字小標是123 132 213 231 321 312依次將第一個位子的元素與2、3交換後,然後直接輸出a[1]a[2]a[3] a[1]a[3]a[2] a[2]a[1]a[3] ...挺有意思。

表達能力有限,可能講得不夠清楚,(雖然我就是這麼想的,) 看到了要是讓你有疑惑,可以指出(當然更多的是仔細再想想

我盡力討論清楚。

//#include<iostream>
//#include<cstring>
//#include<cstdio>
//using  namespace std;
//char ans[101], s[101];
//bool b[123];//由於b[a[i]],a[i]字母範圍是97~122。 
//int len;
//
//void dfs(int  n) {
//	if(n == len) {
//		for(int i = 0; i < len; ++i)
//			cout << ans[i];
//		cout << endl;
//	}
//	else //考慮有else與沒else的區別#1
//		for(int j = 0; j < len; j++){
//			if(!b[s[j]]){
//				b[s[j]] = 1;
//				ans[n] = s[j];
//				dfs(n+1);
//				b[s[j]] = 0;
//			}
//		}
//}
//int main() {
//	cin >> s;
//	len = strlen(s);
//	dfs(0);
//}

/*
	極簡版
*/ 
//#include<iostream>
//#include<cstring>
//using namespace std;
//char s[100];
//int c[100], b[100];//記錄排好序的字母的下標順序 
//int len;
//
//int dfs(int n) {
//	if(n == len){
//		for(int i = 0; i < len; i++)
//			cout << s[c[i]];
//		cout << endl;
//	}
//	for(int i = 0; i < len; i++) {
//		if(!b[i]) {
//			b[i] = 1;
//			c[n] = i;
//			dfs(n+1);
//			b[i] = 0;
//		}
//	}
//}
//int main() {
//	cin >> s;
//	len = strlen(s);
//	dfs(0);
//}

#include<iostream>
#include<cstring>
using namespace std;
char s[100];
int c[100], b[100];//記錄排好序的字母的下標順序 
int len;

int dfs(int n) {
	for(int i = 0; i < len; i++) {
		if(!b[i]) {
			b[i] = 1;
			c[n] = i;
			dfs(n+1);
			b[i] = 0;
		}
	}
	
	if(n == len - 1){//if可以寫到後面,但條件需變動,想想本質的區別#2 
		for(int i = 0; i < len; i++)
			cout << s[c[i]];
		cout << endl;
	}

}
int main() {
	cin >> s;
	len = strlen(s);
	dfs(0);
}

//#include<iostream>
//#include<cstring>
//using namespace std;
//char s[101];
//int c[101], b[101], len;
//int dfs(int n) {
//	for(int i = 0; i < len; ++i) {
//		if(b[i] == 0) {
//			b[i] = 1;
//			c[n] = i;
//			if(n == len - 1){
//				for(int i = 0; i < len; i++)
//					cout << s[c[i]];
//				cout << endl;
//			}
//			else
//				dfs(n+1);
//			b[i] = 0;
//		}
//	}
//}
//int main(){
//	cin >> s;
//	len = strlen(s);
//	dfs(0);
//}

 
//#include<iostream>
//#include<cstring>
//using namespace std;
//char a[100];
//int len;
////不能字典序呈現 
//void swap(char *a, char *b) { 
//	char temp;
//	temp = *a;
//	*a = *b;
//	*b = temp;
//} 
//int f(int n){
//	if(n == len) {
//		for(int i = 0; i < len; ++i)
//			cout << a[i];
//		cout << endl;
//	}
//	for(int i = n; i < len ; i++) {
//		swap(a[i], a[n]);
//		f(n+1);
//		swap(a[i], a[n]);
//	}
//}
//
//int main() {
//	scanf("%s", a);
//	len = strlen(a);
//	f(0);
//}

//下面這時錯誤的程式碼,最開始,思路不清晰時寫下的,回過頭來看可以反映些問題
//#include<cstring>
//using namespace std;
//
//int a[5] = {0};
//int k = 0;
//
//void f(int n) { 
//	while(k < 5 && a[n] == 1) {
//		n = (n + 1) % 5;
//		k++;
//	}
//	if(k == 5) {
//		cout << endl;
//		k = 0;
//		memset(a, 0, sizeof(a));
//		return;
//	}
//	cout << n;
//	k++;
//	for(int i = n; i < 5; i++) {
//		a[n] = 1;
//		f(n+1);
//		a[n] = 0;
//	}
//
//}
//
//int main() {
//	f(0);
//}

思考: #1:不管由還是沒有if都會執行,但判斷成立後,沒else則會繼續執行後面的語句, 雖然這裡沒影響但如果本意不希望再執行下面的程式碼則需用else.  #2噹噹前引數n等於len-1時,也就意味這上面的for迴圈將填滿最後一個位置的數, for迴圈結束後就得輸出結果(這裡再次體現該函式的意義——在a[i]的第n位(0~len-1)上填滿足條件的數. 

//小經驗:

//1.當被變數弄混淆時,需弄清各變數在程式碼中的含義 ,各施其職,得分清後再考慮去,一語雙關。 //2.遞迴實現時,的結果輸出需注意,你確定是要在每次呼叫過程中同時來輸出答案嗎(還希望每次換行後, //當前輸出的前面自動cout出之前的結果。)?還是整理好思路方法,一併輸出答案呢。