《编程之法》1.3字符串的全排列,组合,重复排列,八皇后问题

《编程之法》1.3字符串的全排列,组合,重复排列,八皇后问题

http://blog.csdn.net/qibofang/article/details/51920444

原创 2016年07月15日 19:24:19

题目描述:输入一个字符串,打印出该字符串中字符的所有排列,例如输入"abc",输出"abc","acb","bac","bca","cab","cba"

解法一:递归实现
类似于图的深度遍历搜索求全路径的算法,每次交换两个数,并输出,按照递归的方法,如求abcd的全排序,1:先求abcd后面的bcd全排列(同样先求b后面cd的全排列,然后b与后面的元素依次交换);2:求ab交换后的bacd后面的acd全排列(同样先求a后面cd的全排列,然后a与后面的元素依次交换);3:先ac交换后的cbad后面的bad全排列(同样先求b后面ad的全排列,然后b与后面的元素依次交换);4:求ad交换后的dbca后面的bca全排列(同样先求b后面ca的全排列,然后b与后面的元素依次交换)。
时间复杂度为O(n!)。

 

[cpp] view plain copy
 
  1. #include <iostream>  
  2. #include <string>  
  3. using namespace std;  
  4.   
  5. void AllPermutation(string &str, int start, int end){  
  6.     if(start == end){  
  7.         cout << str << endl;  
  8.         return;  
  9.     }  
  10.     int i;  
  11.     for(i = start; i <= end; i++){  
  12.         swap(str[start], str[i]);  
  13.         AllPermutation(str, start+1, end);  
  14.         swap(str[start], str[i]);  
  15.     }  
  16. }  
  17.   
  18. int main(){  
  19.     string str;  
  20.     while(cin >> str){  
  21.         if(str.size() == 0)//字符串长度为0直接返回  
  22.             break;  
  23.         AllPermutation(str, 0, str.size()-1);  
  24.     }  
  25.     return 0;  
  26. }  
解法二:按字典序排列
如输出zaf的全排列,先sort排序为afz,然后按字典序依次输出:afz, azf, faz, fza, zaf, zfa。时间复杂度为O(n!)。
求当前字符串的下一个字典序字符串的算法思路:(以967812543为例)
1,找出排列中最后一个连续升序序对的首位位置i;如上面的67,78和12,25都为升序序对,其中25为最后一个升序子序列,故所求i为2所在的下标5。
2,找出排列中i位置之后的最后一个比ai大的位置j;如上面的i=5,ai=2,则i右边比ai大的有5,4,3,选择最后一个比ai大的位置,即j为3所在的下标8。
3,交换i和j所对应的元素,之后将第i+1位到最后的部分翻转,这是因为i是最后一个升序序对首位置,则后面的必定全部是降序排列;如上面先交换2和3,变为967813542,在将下标6开始的剩余子串逆置,变为967813245。
[cpp] view plain copy
 
  1. #include <iostream>  
  2. #include <string>  
  3. #include <algorithm>  
  4. using namespace std;  
  5.   
  6. void AllPermutation(char *str, int num){  
  7.     int i, j;  
  8.     //输出当前序列  
  9.     for(i = 0; i < num; ++i)  
  10.         cout << str[i];  
  11.     cout << endl;  
  12.     //寻找下一字典序序列  
  13.     //寻找i  
  14.     for(i = num-2; i >= 0; --i)  
  15.         if(str[i] < str[i+1])  
  16.             break;  
  17.     //递归结束条件:跳出递归,说明此时str中字符串完全降序  
  18.     if(i < 0)         
  19.         return;  
  20.     //寻找j  
  21.     for(j = num-1; j >= i+1; --j)  
  22.         if(str[j] > str[i])  
  23.             break;  
  24.     //交换i,j所对应元素,并将下标i后面的字符串逆置  
  25.     swap(str[i], str[j]);  
  26.     reverse(str+i+1, str+num); //和sort函数一样为标准函数模板调用  
  27.     AllPermutation(str, num);  
  28. }  
  29.   
  30. int main(){  
  31.     char str[110];  
  32.     while(cin >> str){  
  33.         int num = 0;  
  34.         while(str[num]) ++num; //注意别写成while(str[num++]);  
  35.         if(num == 0)//字符串长度为0直接返回  
  36.             break;  
  37.         sort(str, str+num);//注意要先排序  
  38.         AllPermutation(str, num);  
  39.     }  
  40.     return 0;  
  41. }  

 

举一反三:

1,字典序的所有排列:已知输入字符串的各个字符是不同的,按照字典序输出它的所有组合。如输入aa,则输出aa,ab,ba,bb。
解法一:
先分析abc: 输出aaa,aab,aac,aba,abb,abc,aca,acb,acc,baa......,可以分析一下规律
先分配一个数组ans[]存放新字符串,同样运用递归,递归的临界条件是输出字符串中所有字符都相等,且该字符为原字符串最大的那个字符,也先要sort原字符串str[],很显然第一个ans全由最小的字符组成,如ans[] = {a,a,a...a}。
如何求当前字符串的下一字典序字符串:
先在str[]中设置一个全局升序指针j用于循环向右移动,赋初值指向str[]中第二个字符
a,若ans[]的最后一个元素小于str[n-1](字符串中最大元素),交换str[j]和ans[num-1],之后j前移并进入下次递归;如aab变为aac,aba变为abb
b, 若ans[]的最后一个元素等于于str[n-1],则可调用一个自定义函数,函数的作用是指针i指向ans[]最后一个位置,使i所指元素ans[]变为str[0],并令指针i前移,若ans[i]仍然等于str[i-1],令其等于str[0]后,则继续前移至ans[i]<str[i-1],此时将str[i]赋值成一个稍微比他大的那个字符,并且此时重新令j=0;如acc变为baa。
算法复杂度,由于需要输出n^n个结果,且每次递归都要遍历一次数组,则时间复杂度为O(n^n*n) = O(n^n)。
[cpp] view plain copy
 
  1. #include <iostream>  
  2. #include <string>  
  3. #include <algorithm>  
  4. using namespace std;  
  5. int j;  
  6. char str[110], ans[110];  
  7.   
  8. void Access(int cur, int num){  
  9.     while(ans[cur] == str[num-1]){  
  10.         ans[cur] = str[0];  
  11.         --cur;  
  12.     }  
  13.     int i;  
  14.     for(i = 0; i < num; i++)  
  15.         if(ans[cur] == str[i])  
  16.             break;  
  17.     ans[cur] = str[i+1];  
  18.     j = 0;  
  19. }  
  20. void AllPermutation(int num){  
  21.     int i;  
  22.     //先输出当前字符串  
  23.     for(i = 0; i < num; ++i)  
  24.         cout << ans[i];  
  25.     cout << endl;  
  26.     //临界条件  
  27.     int flag = 0;  
  28.     for(i = 0; i < num; ++i)  
  29.         if(ans[i] != str[num-1])  
  30.             flag = 1;  
  31.     if(!flag)  
  32.         return;  
  33.     //交换ans[num-1],str[j]  
  34.     if(ans[num-1] < str[num-1])  
  35.         ans[num-1] = str[j];  
  36.     else  
  37.         Access(num-1, num);  
  38.     ++j;  
  39.     if(j == num)  
  40.         j = 0;  
  41.     AllPermutation(num);  
  42. }  
  43.   
  44. int main(){  
  45.     while(cin >> str){  
  46.         int num = 0;  
  47.         while(str[num]) ++num; //注意别写成while(str[num++]);  
  48.         if(num == 0)//字符串长度为0直接返回  
  49.             break;  
  50.         sort(str, str+num);  
  51.         int i;  
  52.         for(i = 0; i < num; ++i)  
  53.             ans[i] = str[0];  
  54.         j = 1;  
  55.         AllPermutation(num);  
  56.     }  
  57.     return 0;  
  58. }  

2,字符的所有组合:如输入“abc”,输出"a","b","c","ab","ac","bc","abc"。

解法一:我们要分开求长度为1的组合,长度为2的组合,...,长度为n的组合。先考虑其中一种情况,如长度为k时的情况,这个时候我们又可以分为两种情况进行考虑:
a, 如果组合里包含第一个字符,则从所有剩余n-1个字符中选取k-1个字符;
b, 如果组合中不包含第一个字符,则从剩余的n-1个字符中选取k个字符。
这可以使用递归方式解决,使用vector来保存已加入当前组合的值,如下,记住Combination这个模式(临界判断-->输出并返回-->加入当前字符并递归-->去掉当前字符并递归),该模式在求多个数相加等于固定数时也会用到。
有两种返回条件:1,如1,2,3,4,5中当求长度为3的组合时,此时vector只收集了4,5,很显然再递归会造成下标溢出;2,正好某次递归中k==0,数组中可能还有字符也可能正好没有了,此时并直接输出,若正好此时pos=num-1,再次递归也会造成下标溢出,最好是返回。
[cpp] view plain copy
 
  1. #include <iostream>  
  2. #include <vector>  
  3. using namespace std;  
  4. vector<char> result;  
  5. int num;  
  6. void Combination(char *str, int pos, int k){  
  7.     if(pos == num && k != 0)//有两种返回条件:临界条件  
  8.         return;  
  9.     if(k == 0){  
  10.         vector<char>::iterator iter = result.begin();  
  11.         for( ; iter < result.end(); ++iter)  
  12.             cout << *iter;  
  13.         cout << endl;  
  14.         return; //此时也需返回,否则str[pos]=str[num]可能未定义  
  15.     }  
  16.     result.push_back(str[pos]);  
  17.     Combination(str, pos+1, k-1); //从字符串下标pos+1起的后面选择剩余的k-1个字符  
  18.     result.pop_back();  
  19.     Combination(str, pos+1, k); //从字符串下标pos+1起的后面选择剩余的k个字符  
  20. }  
  21.   
  22. int main(){  
  23.     char str[110];  
  24.     while(cin >> str){  
  25.         num = 0;  
  26.         while(str[num]) num++;  
  27.         if(num == 0) break;  
  28.         int i;  
  29.         for(i = 1; i <= num; ++i)  
  30.             Combination(str, 0, i);  
  31.     }  
  32.     return 0;  
  33. }  
解法二:以abc为例,若三个字符a,b,c分别对应一个二进制数的第0,1,2位,则可用二进制001代表"a",二进制010代表"b",二进制011代表"ab",二进制100代表"c",...111代表"abc",换句话说,1~7中的任何一个数字正好对应原字符串的一个组合。
根据数学知识:长度为n的字符串的组合数 = C1/n + C2/n + ... + Cn/n = 2^n - C0/n = 2^n - 1;,故只需对1~2^n-1的数进行遍历,对于该范围的某个k,分析它的二进制位在0~n-1位有哪些为1,则输出str[]中对应的字符;哪些为0,则跳过。
[cpp] view plain copy
 
  1. #include <iostream>  
  2. #include <cmath>  
  3. using namespace std;  
  4. void Combination(char *str, int n){  
  5.     int num = (int)pow(2.0, n) - 1;  
  6.     int i, j;  
  7.     for(i = 1; i <= num; ++i){  
  8.         for(j = 0; j < n; j++){  
  9.             if(i & (1 << j)) //注意:&是按位与,&&是逻辑与(整体逻辑判断)  
  10.                 cout << str[j];  
  11.         }  
  12.         cout << endl;  
  13.     }  
  14. }  
  15.   
  16. int main(){  
  17.     char str[110];  
  18.     while(cin >> str){  
  19.         int n = strlen(str);  
  20.         Combination(str, n);  
  21.     }  
  22.     return 0;  
  23. }  

 

相关题目:

1,求正方体对面顶点和相等数组:输入一个含有8个数字的数组分别置于正方体的8个顶点上,使得正方体三组相对的面上的4个顶点和都相等,即:
a1+a2+a3+a4=a5+a6+a7+a8; a1+a3+a5+a7=a2+a4+a6+a8; a1+a2+a5+a6=a3+a4+a7+a8。
解法:对这8个数全排列,对每个全排列进行条件判断,判断成立则输出,否则什么也不做。

 

[cpp] view plain copy
 
  1. #include <iostream>  
  2. using namespace std;  
  3. bool IsEqual(int nums[]){  
  4.     int sum1 = nums[0] + nums[1] + nums[2] + nums[3];  
  5.     int sum2 = nums[4] + nums[5] + nums[6] + nums[7];  
  6.     int sum3 = nums[0] + nums[2] + nums[4] + nums[6];  
  7.     int sum4 = nums[1] + nums[3] + nums[5] + nums[7];  
  8.     int sum5 = nums[0] + nums[1] + nums[4] + nums[5];  
  9.     int sum6 = nums[2] + nums[3] + nums[6] + nums[7];  
  10.     if(sum1 == sum2 && sum3 == sum4 && sum5 == sum6)  
  11.         return true;  
  12.     else  
  13.         return false;  
  14. }  
  15.   
  16. void AllPermutation(int *nums, int start, int end){  
  17.     if(start == end){  
  18.         if(IsEqual(nums)){  
  19.             int i;  
  20.             for(i = 0; i <= end; ++i)  
  21.                 cout << nums[i] << " ";  
  22.             cout << endl;  
  23.         }  
  24.         return;  
  25.     }  
  26.     int i;  
  27.     for(i = start; i <= end; i++){  
  28.         swap(nums[start], nums[i]);  
  29.         AllPermutation(nums, start+1, end);  
  30.         swap(nums[start], nums[i]);  
  31.     }  
  32. }  
  33.   
  34. int main(){  
  35.     int nums[110], n;  
  36.     while(cin >> nums[0]){ //输入8个数表示正方体的8个顶点  
  37.         int i;  
  38.         for(i = 1; i <= 7; ++i)  
  39.             cin >> nums[i];  
  40.         AllPermutation(nums, 0, 7);  
  41.     }  
  42.     return 0;  
  43. }  
2,八皇后问题
问题描述: 在8 X 8的国际象棋上摆放八个皇后,使其不能相互攻击,即任意两个皇后不得处于同一行,同一列或者同一对角线上,求出所有符合条件的摆法。
问题分析: 任意两个皇后不得处于同一行,由此可得每个皇后都单独占据一行。我们可以定义一个数组ColumnIndex[8],其中ColumnIndex[i]表示处在第i行位置的那个皇后对应在ColumnIndex[i]列,例如ColumnIndex1 = 3 表示处在第1行的皇后在第3列上。 接下来,分别用0~7这8个数字对ColumnIndex进行初始化。注意,此时所有的皇后不同行也不同列。 因此,我们只需要对ColumnIndex数组进行全排列,判断每一个排列所对应的8个皇后的位置是否在对角线上即可。

 

[cpp] view plain copy
 
  1. #include <iostream>  
  2. #include <string>  
  3. using namespace std;  
  4. bool IsNoDiag(int *nums){  
  5.     int i, j;  
  6.     for(int i = 0; i < 8; i++){  
  7.         for(int j = i + 1; j < 8; j++){  
  8.             if(i - j == nums[i] - nums[j] || i - j == nums[j] - nums[i])  
  9.                 return false;  
  10.         }  
  11.     }  
  12.     return true;;  
  13. }  
  14.   
  15. void AllPermutation(int *nums, int start, int end){  
  16.     //临界条件  
  17.     if(start == end){  
  18.         if(IsNoDiag(nums)){  
  19.             int i;  
  20.             for(i = 0; i <= end; ++i)  
  21.                 cout << "(" << i << ", " << nums[i] << ")" << " ";  
  22.             cout << endl;  
  23.         }  
  24.         return;  
  25.     }  
  26.     //下次递归  
  27.     int i;  
  28.     for(i = start; i <= end; ++i){  
  29.         swap(nums[start], nums[i]);  
  30.         AllPermutation(nums, start+1, end);  
  31.         swap(nums[start], nums[i]);  
  32.     }  
  33. }  
  34.   
  35. int main(){  
  36.     int nums[8] = {0, 1, 2, 3, 4, 5, 6, 7};  
  37.     AllPermutation(nums, 0, 7);  
  38. }  

 

#include <iostream>  
#include <string>  
using namespace std;  

int count=0;
bool IsNoDiag(int *nums){  
    int i, j;  
    for(int i = 0; i < 8; i++){  
        for(int j = i + 1; j < 8; j++){  
            if(i - j == nums[i] - nums[j] || i - j == nums[j] - nums[i])  
                return false;  
        }  
    }  
    return true;;  
}  

void AllPermutation(int *nums, int start, int end){  
    //临界条件  
    if(start == end){  
        if(IsNoDiag(nums)){  
            count+=1;
            int i;  
            for(i = 0; i <= end; ++i)  
                cout << "(" << i << ", " << nums[i] << ")" << " ";  
            cout << endl;  
        }  
        return;  
    }  
    //下次递归  
    int i;  
    for(i = start; i <= end; ++i){  
        swap(nums[start], nums[i]);  
        AllPermutation(nums, start+1, end);  
        swap(nums[start], nums[i]);  
    }  
}  

int main(){  
    int nums[8] = {0, 1, 2, 3, 4, 5, 6, 7};  
    AllPermutation(nums, 0, 7);  
    printf("count:%d\n",count);
} 

/*
count:92
请按任意键继续. . .
*/

 

posted @ 2018-02-05 11:16  sky20080101  阅读(74)  评论(0)    收藏  举报