標籤:style blog http color 使用 io for art
53.字串的排列(字串)。
題目:輸入一個字串,列印出該字串中字元的所有排列。
例如輸入字串 abc,則輸出由字元 a、b、c 所能排列出來的所有字串
abc、acb、bac、bca、cab 和 cba。
這道題花了我一天,要好好總結!
思路:這道題目感覺有些難,主要是字串中的字元可能會有重複。我的想法是把一共有多少種字元和每種字元出現的次數統計出來,每個位置對這些字元變數,下一個位置的可用字元減小,再遍曆。
/*53.字串的排列(字串)。題目:輸入一個字串,列印出該字串中字元的所有排列。例如輸入字串 abc,則輸出由字元 a、b、c 所能排列出來的所有字串abc、acb、bac、bca、cab 和 cba。*/#include <stdio.h>#include <string.h>#include <stdlib.h>char A[128][2] = {0}; //統計字串中每種字元出現的次數char ANS[10000]; //可能的輸出int N; //字元種類數int LEN; //輸入字串長度int getTimes(char * in) //統計字元個數 以及 每個字元出現的次數{ int len = strlen(in); bool B[100] = {0}; int n = 0; for(int i = 0; in[i] != ‘\0‘; i++) { if(B[i] == false) { for(int j = i; in[j] != ‘\0‘; j++) { if(in[j] == in[i]) { A[n][0] = in[i]; A[n][1]++; B[j] = true; } } n++; } } return n;}void traverse(int len) //遞迴求解 每個位置遍曆選用A中可用次數大於0的的字元{ if(len == 0) { for(int i = 0; i < LEN; i++) { printf("%c ", ANS[i]); } printf("\n"); return; } for(int i = 0; i < N; i++) { if(A[i][1] != 0) //如果不等於0 說明該字元還可以使用 { ANS[LEN - len] = A[i][0]; //把當前字元存在答案中 A[i][1]--; //當前字元可用次數減1 traverse(len - 1); //求下一個位置的字元 A[i][1]++; //恢複當前字元的可使用次數 } } }void getall(char * in){ LEN = strlen(in); N = getTimes(in); traverse(LEN);}int main(){ char in[100] = "abcc"; getall(in); return 0;}
寫完後就覺得自己寫的不好,好多全域變數,看著難受。
網上看到了很好的解答:http://blog.csdn.net/hackbuteer1/article/details/7462447
給出了遞迴和非遞迴的思路。我看完後也仿照著思路重寫了一遍,又照著修改了下。
二、去掉重複的全排列的遞迴實現
由於全排列就是從第一個數字起每個數分別與它後面的數字交換。我們先嘗試加個這樣的判斷——如果一個數與後面的數字相同那麼這二個數就不交換了。如122,第一個數與後面交換得212、221。然後122中第二數就不用與第三個數交換了,但對212,它第二個數與第三個數是不相同的,交換之後得到221。與由122中第一個數與第三個數交換所得的221重複了。所以這個方法不行。
換種思維,對122,第一個數1與第二個數2交換得到212,然後考慮第一個數1與第三個數2交換,此時由於第三個數等於第二個數,所以第一個數不再與第三個數交換。再考慮212,它的第二個數與第三個數交換可以得到解決221。此時全排列產生完畢。
這樣我們也得到了在全排列中去掉重複的規則——去重的全排列就是從第一個數字起每個數分別與它後面非重複出現的數字交換。
三、全排列的非遞迴實現
要考慮全排列的非遞迴實現,先來考慮如何計算字串的下一個排列。如"1234"的下一個排列就是"1243"。只要對字串反覆求出下一個排列,全排列的也就迎刃而解了。
如何計算字串的下一個排列了?來考慮"926520"這個字串,我們從後向前找第一雙相鄰的遞增數字,"20"、"52"都是非遞增的,"26 "即滿足要求,稱前一個數字2為替換數,替換數的下標稱為替換點,再從後面找一個比替換數大的最小數(這個數必然存在),0、2都不行,5可以,將5和2交換得到"956220",然後再將替換點後的字串"6220"顛倒即得到"950226"。
對於像“4321”這種已經是最“大”的排列,採用STL中的處理方法,將字串整個顛倒得到最“小”的排列"1234"並返回false。
這樣,只要一個迴圈再加上計算字串下一個排列的函數就可以輕鬆的實現非遞迴的全排列演算法。按上面思路並參考STL中的實現源碼,不難寫成一份品質較高的代碼。值得注意的是在迴圈前要對字串排序下,可以自己寫快速排序的代碼
/*http://blog.csdn.net/hackbuteer1/article/details/7462447在上面的網址上看了答案後 發現自己寫的代碼實在是太挫了,有很多不必要的全域變數。還有輸入字元中一共有幾種字元,每種字元的出現次數其實可以不用具體求出來的。直接比較判斷就好了。網上還給出了非遞迴演算法,找到了從小到大輸出所有可能組合的方法,非常值得學習。*///遞迴#include<stdio.h>#include<string.h>#include <algorithm>using namespace std;bool isswap(char * pBegin, char * pNow){ for(char *p = pBegin; p < pNow; p++) { if(*p == *pNow) return false; } return true;}void traverse(char * pStr,char * pBegin){ if(/**pBegin == ‘\0‘*/strlen(pBegin) == 1) //兩種判斷方式都可以 { static int n = 0; printf("%d: %s\n", ++n, pStr); } else { for(char *p = pBegin; *p != ‘\0‘; p++) { if(isswap(pBegin, p)) { swap(*pBegin, *p); //注意swap應該是 數值 而不是指標 traverse(pStr, pBegin + 1); swap(*p, *pBegin); } } }}//非遞迴int cmp(const void * a,const void * b){ return *((char *)a) - *((char *)b);}void reverse(char * pBegin, char * pEnd){ while(pBegin < pEnd) { swap(*pBegin++, *pEnd--); }}bool nonrtraverse(char * pStr){ int l = strlen(pStr); char * pEnd = pStr + l - 1; //定位最後一個字元 if(pStr == pEnd) //注意對只有一個字元的處理 return false; for(char *p = pEnd; p != pStr; p--) { char * q = p - 1; if(*q < *p) { char* pFind = pEnd; while(*pFind <= *q) //注意這裡,找到第一個比交換點數大的數 --pFind; swap(*q, *pFind); reverse(p, pEnd); return true; } } reverse(pStr, pStr +l - 1); return false;}int main(){ char str[30] = "1224"; traverse(str, str); qsort(str, strlen(str)-1, sizeof(char), cmp); int i = 0; do { printf("%d:%s\n", ++i, str); } while(nonrtraverse(str)); return 0;}