例如:输入"abc",输出结果为abc, acb, bac, bca, cab和cba。
方法一:比较笨,用一个整型数组,记录当前排列的下标,然后输出下标对应的字符串,空间代价为O(n)
代码:
//参数:字符串,记录当前全排列组合的字符在字符串中的下标,字符串长度,当前要确定的字符 void Permutation(char* letter, int* record, int length, int nCur) { if ( nCur == length ) { static int count = 0; count++; cout << count << "->:\t "; for ( int i = 0; i < length; i++ ) { cout << letter[record[i]]; } cout << endl; return; } for ( int i = 0; i < length; i++ ) { //确定当前结点的字符是什么,不能和已经确定的字符相同 int j; for ( j = 0; j < nCur; j++ ) { if ( i==record[j] ) break; } if ( j != nCur ) continue; //确定当前字符 if ( j == nCur ) record[nCur] = i; Permutation(letter,record,length,nCur+1); } }
int _tmain(int argc, _TCHAR* argv[]) { char* letter = "abc"; int length = strlen(letter); int* record = new int[length]; Permutation(letter,record,length,0); return 0; }方法二:
通过找规律,求字符串的全排列,可以看出三步:
首先,求所有可能出现在第一个位置的字符,
其次,把第一个字符和其后面的字符一一交换。如下图所示,分别把第一个字符a和后面的b、c等字符交换的情形。
接着,固定第一个字符,求后面所有字符的排列。这个时候我们仍把后面的所有字符分成两部分:后面字符的第一个字符,以及这个字符之后的所有字符。然后把第一个字符逐一和它后面的字符交换
代码:
void Permutation_o(char* pStr, char* pBegin) { if (*pBegin == '\0' ) cout << pStr <<endl; else { for (char* pCh = pBegin; *pCh!='\0'; pCh++) { char temp = *pCh; *pCh = *pBegin; *pBegin = temp; Permutation_o(pStr, pBegin+1); temp = *pCh; *pCh = *pBegin; *pBegin = temp; } } } void Permutation_o(char* pStr) { if (pStr==NULL) return; Permutation_o(pStr,pStr); } int main(void) { char str[10] ; cout <<"input the string: "; cin>> str; cout <<"the string's permutation lists:"<<endl; Permutation_o(str); return 0; }
方法三:
由于,我们担心递归的高效性,很多把递归算法改写成非递归算法。但是比较难,但是STL中有一个函数next_permutation(),它的作用是如果对于一个序列,存在按照字典排序后这个排列的下一个排列,那么就返回ture,并且产生这个排列,否则返回false。注意,为了产生这个全排列,这个序列必须是有序的,即调用一次sort函数。
代码:
#include "stdafx.h" #include <algorithm> #include <iostream> using namespace std; void Permutation(char* pStr, int length) { sort(pStr,pStr+length); do { cout << pStr <<endl; } while ( next_permutation(pStr,pStr+length) ); } int _tmain(int argc, _TCHAR* argv[]) { char str[10]; cout << "Input the string:"; cin >> str; int lengh = strlen(str); cout << "Permutation is :"<<endl; Permutation(str,lengh); return 0; }
例如:输入"abb",输出结果为abb, bab, bba。
方法一:
思路:在上面的代码基础之上稍加修改即可。我们可以用一个hash记录已经存在的全排列。防止重复输出。
代码:
void Permutation_o(char* pStr, char* pBegin, map<string, string>& Dinstinct) { if (*pBegin == '\0' ) { map <string, string> :: const_iterator iter; string strString(pStr); if ( Dinstinct.find(strString) == Dinstinct.end() ) //不存在 { cout << pStr <<endl; Dinstinct.insert(pair<string, string>(strString,strString)); } } else { for (char* pCh = pBegin; *pCh!='\0'; pCh++) { char temp = *pCh; *pCh = *pBegin; *pBegin = temp; Permutation_o(pStr, pBegin+1, Dinstinct); temp = *pCh; *pCh = *pBegin; *pBegin = temp; } } } void Permutation_o(char* pStr) { if (pStr==NULL) return; map<string, string> Dinstinct; Permutation_o(pStr,pStr,Dinstinct); } int main(void) { char str[10] ; cout <<"input the string: "; cin>> str; cout <<"the string's permutation lists:"<<endl; Permutation_o(str); return 0; }
方法二:
思路:去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。
例如:“abb”,第一个字符a和第二个字符b交换,得到“bab”,此时由于第二个字符和第三个字符相同,所有第一个字符“a”不与第三个字符“b“交换。再考虑,”bab“,第二个字符和第三个字符不同,交换得”bba“,此时结束。生成全部排列
代码:
#include "stdafx.h" #include <iostream> using namespace std; //在[nBegin,nEnd)区间中是否有字符与下标为pEnd的字符相等 bool IsSwap(char* pBegin, char* pEnd) { char* p; for (p=pBegin; p<pEnd; p++) { if (*p == *pEnd) return false; } return true; } void Permutation(char* pStr, char* pBegin) { if (*pBegin == '\0') cout << pStr <<endl; else { for (char* pCh = pBegin; *pCh!='\0'; pCh++) { if( IsSwap(pBegin,pCh) ) { char temp = *pCh; *pCh = *pBegin; *pBegin = temp; Permutation(pStr, pBegin+1); temp = *pCh; *pCh = *pBegin; *pBegin = temp; } } } } void Permutation(char* pStr) { if (pStr==NULL) return; Permutation(pStr,pStr); } int _tmain(int argc, _TCHAR* argv[]) { char str[10]; cout << "Input the string:"; cin >> str; cout << "Permutation is :"<<endl; Permutation(str); return 0; }
方法三:
与上一问题的方法三,代码完全一样,调用STL中的next_permutation函数,可以实现。
方法四:自己用非递归实现,基于STL中next_permutation函数思想
要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。
基本思想是:
1.对初始队列进行排序,找到所有排列中最小的一个排列Pmin。
2.找到刚刚好比Pmin大比其它都小的排列P(min+1)。
3.循环执行第二步,直到找到一个最大的排列,算法结束。
如排列ABCDE,这是所有排列中最小的一个排列,刚好比ABCDE大的排列是:ABCED。算法如下:
给定已知序列P = A1A2A3.....An
对P按字典排序,得到P的一个最小排列Pmin = A1A2A3....An ,满足Ai > A(i-1) (1 < i <= n)
从Pmin开始,找到刚好比Pmin大的一个排列P(min+1),再找到刚好比P(min+1)大的一个排列,如此重复。
1.从后向前(即从An->A1),找到第一对为升序的相邻元素,即Ai < A(i+1)。
若找不到这样的Ai,说明已经找到最后一个全排列,可以返回了。
2.从后向前,找到第一个比Ai大的数Aj,交换Ai和Aj。
3.将排列中A(i+1)A(i+2)....An这个序列的数逆序倒置,即An.....A(i+2)A(i+1)。因为由前面第1、2可以得知,A(i+1)>=A(i+2)>=.....>=An,这为一个升序序列,应将该序列逆序倒置,所得到的新排列才刚刚好比上个排列大。
4.重复步骤1-3,直到返回。
代码:
#include "stdafx.h" #include <iostream> using namespace std; //两字符交换 void Swap(char*a, char* b) { char t = *a; *a = *b; *b = t; } //反转区间 void Reverse(char* a, char* b) { while ( a < b) Swap(a++,b--); } //下一个排列 bool Next_Permutation(char* a) { char *pEnd = a + strlen(a); if (a==pEnd) return false; char *p,*q,*pFind; pEnd--; p = pEnd; while ( p != a ) { q = p; --p; if (*p < *q) //找降序的相邻2数,前一个数即替换数 { //从后向前找比替换点大的第一个数 pFind = pEnd; while (*pFind<=*p) --pFind; //替换 Swap(pFind,p); //替换点后的数全部反转 Reverse(q, pEnd); return true; } } Reverse(p, pEnd); //如果没有下一个排列,全部反转后返回true return false; } int QsortCmp(const void *pa, const void *pb) { return *(char*)pa - *(char*)pb; } int _tmain(int argc, _TCHAR* argv[]) { char str[10]; cout << "Input the string:"; cin >> str; int lengh = strlen(str); qsort(str,lengh, sizeof(char), QsortCmp); cout << "Permutation is :"<<endl; do { cout << str <<endl; } while (Next_Permutation(str)); return 0; }
例如:如果输入abc,它的组合有a、b、c、ab、ac、bc、abc。
方法一:
思路:同样是用递归求解。可以考虑求长度为n的字符串中m个字符的组合,设为C(n,m)。原问题的解即为C(n, 1), C(n, 2),...C(n, n)的总和。对于求C(n, m),从第一个字符开始扫描,每个字符有两种情况,要么被选中,要么不被选中,如果被选中,递归求解C(n-1, m-1)。如果未被选中,递归求解C(n-1, m)。不管哪种方式,n的值都会减少,递归的终止条件n=0或m=0。
代码:
#include "stdafx.h" #include <vector> #include <string> #include <iostream> using namespace std; /* 函数功能:从一个字符串中选m个元素 函数参数:pStr为字符串,m为选的元素个数,result为选中的 无返回值。*/ void Combination_m(char* pStr, int m, vector<char> &result) { //字符串为空,或者长度达不到m if ( pStr==NULL || (*pStr=='\0'&&m!=0) ) return; //出口,输出组合 if (m==0) { for ( unsigned i = 0; i < result.size(); i++ ) cout << result[i]; cout << endl; return; } //如果长度没有达到m //此时选择这个元素 result.push_back(*pStr); Combination_m(pStr+1, m-1, result); result.pop_back(); //不选择这个元素 Combination_m(pStr+1, m, result); } /* 函数功能:求一个字符串的组合 函数参数:pStr为字符串 无返回值*/ void Combination(char* pStr) { if ( pStr==NULL || *pStr=='\0' ) return; int number = strlen(pStr); for ( int i = 1; i <= number; i++ ) { vector<char> result; Combination_m(pStr, i, result); } } int _tmain(int argc, _TCHAR* argv[]) { char* a="abc"; cout << "string a's combination: "<<endl; Combination(a); cout << "string b's combination: "<<endl; char* b="abb"; Combination(b); system("pause"); return 0; }
结果如下:
我们看到,对于有相同字符的字符串,求得的全排列有问题,没去重复的。
方法二:利用位运算来实现求组合
例如:对于字符序列“abc”,让其与1-7的二进制相与,每次输出对应二进制为1的那几位,即可输出全部组合。
代码:
#include "stdafx.h" #include <iostream> using namespace std; void print_subset(char* pStr, int n , int s) { for(int i = 0 ; i < n ; ++i) { if( s & (1<<i) ) // 判断s的二进制中哪些位为1,即代表取某一位 cout << pStr[i] <<" "; } cout <<endl; } void subset(char* pStr, int n) { for(int i= 1 ; i < (1<<n) ; ++i) { print_subset(pStr,n,i); } } int main(void) { char Str[10]; cout<<"Input the string:"; cin>> Str; cout<<"----------"<<endl; subset(Str,strlen(Str)); return 0; }
思路:记录每个组合中每个字符出现的次数,防止下次输出
代码略。
如果有什么好思路,请留言,谢谢交流。