# 排列
字符串“abc”的全排列:
abc
acb
bac
bca
cba
cab
求整个字符串的排列,可以分为两步:
第一步,求所有可能出现在第一个位置的字符,即把第一个字符和后面所有的字符交换。第二步,固定第一个字符,求后面所有字符的全排列。这个时候仍然把后面所有字符分为两部分:第一个字符以及其后所有字符。这是一个典型的递归问题。
例如字符串“abc”的全排列:以a开头的全排列是a{bc},递归得到bc的全排列,而bc的全排列又是以b开头,c的全排列(即c自己);
同样,以b开头的全排列是b{ac},递归得到ac的全排列,而ab的全排列又是以a开头,c的全排列。……
[http://blog.csdn.net/cinderella_niu/article/details/42930281](http://blog.csdn.net/cinderella_niu/article/details/42930281)
### 无重复的全排列 递归方法
如下是递归算法:
~~~
//全排列
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
void Permutation(char str[], int index, int size) {
cout << "Permutation(" << index << ", " << size << ")" << endl;
if (index == size - 1) {
//cout << "全排列:"; //cout << str << endl;由于是全排列,还可以用这句打印结果
for (int i = 0; i < size; i++)
cout << str[i];
cout << endl;
}
else {
for (int i = index; i < size; i++) {
//cout << "\t" << str;
swap(str[index], str[i]);
//cout << " swap(" << index << ","<< i << ") " << str << endl;
Permutation(str, index + 1, size);
//cout << "---\t" << str;
swap(str[index], str[i]);
//cout << " swap(" << index << ","<< i << ") " << str << endl << endl;
}
}
}
~~~
~~~
int main() {
char test[] = "abc";
Permutation(0, sizeof(test) - 1);
}
~~~
下图分别是运行注释内语句和去掉注释内语句的运行结果:
![](https://box.kancloud.cn/2016-06-07_575683c191504.jpg)
![](https://box.kancloud.cn/2016-06-07_575683c1a52a8.jpg)
另一种写法:
~~~
void Permutation(char* pStr, char* pBegin)
{
assert(pStr && pBegin);
if(*pBegin == '\0')
printf("%s\n",pStr);
else
{
for(char* pCh = pBegin; *pCh != '\0'; pCh++)
{
swap(*pBegin,*pCh);
Permutation(pStr, pBegin+1);
swap(*pBegin,*pCh);
}
}
}
~~~
### 有重复的全排列 递归方法
由于全排列就是从第一个数字起每个数分别与它后面的数字交换。我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了。如abb,第一个数与后面交换得bab、bba。然后abb中第二数就不用与第三个数交换了,但对bab,它第二个数与第三个数是不相同的,交换之后得到bba。与由abb中第一个数与第三个数交换所得的bba重复了。所以这个方法不行。
*换种思维*,对abb,第一个数a与第二个数b交换得到bab,然后考虑第一个数a与第三个数b交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。再考虑bab,它的第二个数与第三个数交换可以得到解决bba。此时全排列生成完毕。
这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。用编程的话描述就是第i个数与第j个数交换时,要求[i,j)中没有与第j个数相等的数。
~~~
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
~~~
~~~
//在pszStr数组中,[nBegin,nEnd)中是否有数字与下标为nEnd的数字相等
bool IsSwap(char *pszStr, int nBegin, int nEnd)
{
for (int i = nBegin; i < nEnd; i++)
if (pszStr[i] == pszStr[nEnd])
return false;
return true;
}
//k表示当前选取到第几个数,m表示共有多少数.
void PermutationHaveSameword(char *pszStr, int k, int m)
{
if (k == m)
{
static int s_i = 1;
printf(" 第%3d个排列\t%s\n", s_i++, pszStr);
}
else
{
for (int i = k; i <= m; i++) //第i个数分别与它后面的数字交换就能得到新的排列
{
if (IsSwap(pszStr, k, i))
{
swap(pszStr[k], pszStr[i]);
PermutationHaveSameword(pszStr, k + 1, m);
swap(pszStr[k], pszStr[i]);
}
}
}
}
~~~
~~~
int main(void)
{
char str[] = "aba";
PermutationHaveSameword(str, 0, sizeof(str) - 2);
//Combination(str);
//Permutation(str, 0, sizeof(str) - 1);
return 0;
}
~~~
![](https://box.kancloud.cn/2016-06-07_575683c1b6640.jpg)
### 排列的非递归实习
这个木有懂,参考[http://blog.csdn.net/MoreWindows/article/details/7370155#t2](http://blog.csdn.net/MoreWindows/article/details/7370155)
至此我们已经运用了递归与非递归的方法解决了全排列问题,总结一下就是:
1.全排列就是从第一个数字起每个数分别与它后面的数字交换。
2.去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。
3.全排列的非递归就是由后向前找替换数和替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。
### 全排列的变形:部分排列
问题:从0-9这十个数字中选取3个数字进行排列,打印所有的组合结果。
这个问题就是全排列的变种了,全排列是求出了所有字母的排列方式,该题是求出了部分字母排列的方法。只需要将本文第一个程序“无重复的全排列 递归方法”中,程序结束的条件由if (index == size - 1)改为 if (index == m)即可,其中m是指要进行全排列的字符个数,比如m = 3。
~~~
//全排列
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
//从size个字符中选取m个字符进行全排列,打印排列结果
void Permutation(char str[], int index, int m, int size) {
if (index == m) { //和本文第一个程序比只是这里打印时不太相同
for (int i = 0; i < m; i++)
cout << str[i];
cout << "\t";
}
else {
for (int i = index; i < size; i++) {
swap(str[index], str[i]);
Permutation(str, index + 1, size);
swap(str[index], str[i]);
}
}
}
~~~
测试从0-9中选两个数的排列结果:
~~~
int main() {
char test[] = "0123456789";
Permutation(test, 0, 2, sizeof(test) - 1);
}
~~~
![](https://box.kancloud.cn/2016-06-07_575683c1c69ba.jpg)
# 组合问题
题目:输入一个字符串,输出该字符串中字符的所有组合。举个例子,如果输入abc,它的组合有a、b、c、ab、ac、bc、abc。
长度为n的字符串,能构成长度为1的组合、长度为2的组合、……、长度为n的组合。假设我们想在长度为n的字符串中求m个字符的组合。我们先从头扫描字符串的第一个字符。针对第一个字符,我们有两种选择:第一是把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选取m-1个字符;第二是不把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选择m个字符。这两种选择都很容易用递归实现。
~~~
void Combination(char *string ,int number,vector<char> &result);
void Combination(char *string) {
assert(string != NULL);
vector<char> result;
int i , length = strlen(string);
for(i = 1 ; i <= length ; ++i)
Combination(string , i ,result);
}
//使用vector容器来存放要放入组合的字符
void Combination(char *string ,int number , vector<char> &result) {
assert(string != NULL);
if(number == 0) {
static int num = 1;
printf("第%d个组合\t",num++);
vector<char>::iterator iter = result.begin();
for( ; iter != result.end() ; ++iter)
printf("%c",*iter);
printf("\n");
return ;
}
if(*string == '\0')
return ;
result.push_back(*string);
cout << *string << endl;
Combination(string + 1 , number - 1 , result);
result.pop_back();
Combination(string + 1 , number , result);
}
~~~
![](https://box.kancloud.cn/2016-06-07_575683c1d9984.jpg)
### 使用位运算求组合问题
一个长度为n的字符串,它的组合有2^n-1中情况,我们用1 ~ 2^n-1的二进制来表示,每种情况下输出当前位等于1的字符。
![](https://box.kancloud.cn/2016-06-07_575683c1ea038.jpg)
~~~
void Combination(char *str)
{
int len=strlen(str);
for(int cur=1; cur < (1<<len); cur++) //遍历所有的情况,1<<len就等于2^len,遍历1 ~ 2^len-1
{
for(int j=0; j < len; j++) //遍历所有的字符
{
if(cur & (1 << j)) //判断哪一位为1,即输出该位
cout << str[j];
}
cout << endl; //一种情况结束
}
}
~~~
# 八皇后问题
八皇后问题:[](http://zhedahht.blog.163.com/blog/static/2541117420114331616329/)
### [程序员面试题精选100题(58)-八皇后问题[算法] ](http://zhedahht.blog.163.com/blog/static/2541117420114331616329/ "阅读全文")
# [经典回溯算法(八皇后问题)](http://www.cnblogs.com/gaoteng/archive/2012/04/11/2442692.html)
参考:[http://blog.sina.com.cn/s/blog_6fb300a30100mvzp.html](http://blog.sina.com.cn/s/blog_6fb300a30100mvzp.html)
[http://dongxicheng.org/structure/permutation-combination/](http://dongxicheng.org/structure/permutation-combination/)
[http://blog.csdn.net/hackbuteer1/article/details/7462447](http://blog.csdn.net/hackbuteer1/article/details/7462447)
[http://www.it165.net/pro/html/201412/28579.html](http://www.it165.net/pro/html/201412/28579.html)
### [程序员面试题精选100题(59)-字符串的组合[算法] ](http://zhedahht.blog.163.com/blog/static/2541117420114172812217/ "阅读全文")
- 前言
- Josephus约瑟夫问题及其变种
- 链表的常见实现
- 二叉树遍历、插入、删除等常见操作
- 二叉堆的插入删除等操作C++实现
- 插入排序和希尔排序
- 堆排序
- 归并排序及其空间复杂度的思考
- 快速排序的几种常见实现及其性能对比
- 红黑树操作及实现
- 整数的二进制表示中1的个数
- 位操作实现加减乘除四则运算
- 冒泡排序的改进
- 直接选择排序
- 不借助变量交换两个数
- 基础排序算法总结
- AVL树(Adelson-Velskii-Landis tree)
- avl树的C++实现
- 动态规划之钢条分割
- hash函数的基本知识
- 动态规划:求最长公共子串/最长公共子序列
- 最长递增子序列
- 称砝码问题
- 汽水瓶
- 字符串合并处理(二进制位的倒序)
- 动态规划:计算字符串相似度
- m个苹果放入n个盘子
- 生成k个小于n的互不相同的随机数
- 栈和队列的相互模拟
- 字符串的排列/组合
- KMP(Knuth-Morris-Pratt)算法
- n个骰子的点数
- 位运算的常见操作和题目