全排列 遞迴(非字典序) 深搜(字典序)
全排列問題初探,不含重複元素情況的討論。
糊的題目:
【題目描述】
給定一個由不同的小寫字母組成的字串,輸出這個字串的所有全排列。
我們假設對於小寫字母有‘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出之前的結果。)?還是整理好思路方法,一併輸出答案呢。