字符串的全排列和组合算法

全排列在笔试面试中很热门,因为它难度适中,既可以考察递归实现,又能进一步考察非递归的实现,便于区分出考生的水平。所以在百度和迅雷的校园招聘以及程序员和软件设计师的考试中都考到了,因此本文对全排列作下总结帮助大家更好的学习和理解。对本文有任何补充之处,欢迎大家指出。

首先来看看题目是如何要求的(百度迅雷校招笔试题)。

(转载:http://blog.csdn.net/hackbuteer1/article/details/7462447)

一、字符串的排列
用C++写一个函数, 如 Foo(const char *str), 打印出 str 的全排列,
如 abc 的全排列: abc, acb, bca, dac, cab, cba

一、全排列的递归实现

为方便起见,用123来示例下。123的全排列有123、132、213、231、312、321这六种。首先考虑213和321这二个数是如何得出的。显然这二个都是123中的1与后面两数交换得到的。然后可以将123的第二个数和每三个数交换得到132。同理可以根据213和321来得231和312。因此可以知道——全排列就是从第一个数字起每个数分别与它后面的数字交换。找到这个规律后,递归的代码就很容易写出来了:

#include<iostream>
using namespace std;
#include<assert.h>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);}}
}int main(void)
{char str[] = "abc";Permutation(str,str);return 0;
}
另外一种写法:
</pre><pre name="code" class="cpp"><pre name="code" class="cpp">//k表示当前选取到第几个数,m表示共有多少个数
#include <iostream>
#include <cstring>
#include <cassert>
using namespace std;
void Permutation(char* pStr,int k,int m)
{assert(pStr);if(k == m){static int num = 1;  //局部静态变量,用来统计全排列的个数printf("第%d个排列\t%s\n",num++,pStr);}else{for(int i = k; i < m; i++){swap(*(pStr+k),*(pStr+i));Permutation(pStr, k + 1 , m);swap(*(pStr+k),*(pStr+i));}}
}int main(void)
{char str[] = "abc";Permutation(str , 0 , strlen(str));return 0;
}


 
如果字符串中有重复字符的话,上面的那个方法肯定不会符合要求的,因此现在要想办法来去掉重复的数列。

二、去掉重复的全排列的递归实现
由于全排列就是从第一个数字起每个数分别与它后面的数字交换。我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了。如122,第一个数与后面交换得212、221。然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,交换之后得到221。与由122中第一个数与第三个数交换所得的221重复了。所以这个方法不行。

换种思维,对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。再考虑212,它的第二个数与第三个数交换可以得到解决221。此时全排列生成完毕。
这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。下面给出完整代码:

#include<iostream>using namespace std;#include<assert.h>//在[nBegin,nEnd)区间中是否有字符与下标为pEnd的字符相等,也就是在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){assert(pStr);if(*pBegin == '\0'){static int num = 1;  //局部静态变量,用来统计全排列的个数printf("第%d个排列\t%s\n",num++,pStr);}else{for(char *pCh = pBegin; *pCh != '\0'; pCh++)   //第pBegin个数分别与它后面的数字交换就能得到新的排列   {if(IsSwap(pBegin , pCh)){swap(*pBegin , *pCh);Permutation(pStr , pBegin + 1);swap(*pBegin , *pCh);}}}}int main(void){char str[] = "baa";Permutation(str , str);return 0;}OK,到现在我们已经能熟练写出递归的方法了,并且考虑了字符串中的重复数据可能引发的重复数列问题。那么如何使用非递归的方法来得到全排列了?三、全排列的非递归实现要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",然后再将替换点后的字符串"6220"颠倒即得到"950226"。对于像“4321”这种已经是最“大”的排列,采用STL中的处理方法,将字符串整个颠倒得到最“小”的排列"1234"并返回false。

这样,只要一个循环再加上计算字符串下一个排列的函数就可以轻松的实现非递归的全排列算法。按上面思路并参考STL中的实现源码,不难写成一份质量较高的代码。值得注意的是在循环前要对字符串排序下,可以自己写快速排序的代码(请参阅《白话经典算法之六 快速排序 快速搞定》),也可以直接使用VC库中的快速排序函数(请参阅《使用VC库函数中的快速排序函数》)。下面列出完整代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#include<assert.h>//反转区间
void Reverse(char* pBegin , char* pEnd)
{while(pBegin < pEnd)swap(*pBegin++ , *pEnd--);
}
//下一个排列
bool Next_permutation(char a[])
{assert(a);char *p , *q , *pFind;char *pEnd = a + strlen(a) - 1;if(a == pEnd)return false;p = pEnd;while(p != a){q = p;p--;if(*p < *q)  //找降序的相邻2数,前一个数即替换数  {//从后向前找比替换点大的第一个数pFind = pEnd;while(*pFind < *p)--pFind;swap(*p , *pFind);//替换点后的数全部反转Reverse(q , pEnd);return true;}}Reverse(a , pEnd);   //如果没有下一个排列,全部反转后返回false   return false;
}int cmp(const void *a,const void *b)
{return int(*(char *)a - *(char *)b);
}
int main(void)
{char str[] = "bac";int num = 1;qsort(str , strlen(str),sizeof(char),cmp);do{printf("第%d个排列\t%s\n",num++,str); }while(Next_permutation(str));return 0;
}


至此我们已经运用了递归与非递归的方法解决了全排列问题,总结一下就是:
1、全排列就是从第一个数字起每个数分别与它后面的数字交换。
2、去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。
3、全排列的非递归就是由后向前找替换数和替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。

二、字符串的组合

题目:输入一个字符串,输出该字符串中字符的所有组合。举个例子,如果输入abc,它的组合有a、b、c、ab、ac、bc、abc。

上面我们详细讨论了如何用递归的思路求字符串的排列。同样,本题也可以用递归的思路来求字符串的组合。

假设我们想在长度为n的字符串中求m个字符的组合。我们先从头扫描字符串的第一个字符。针对第一个字符,我们有两种选择:第一是把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选取m-1个字符;第二是不把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选择m个字符。这两种选择都很容易用递归实现。下面是这种思路的参考代码:

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
#include<assert.h>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);
}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);Combination(string + 1 , number - 1 , result);result.pop_back();Combination(string + 1 , number , result);
}int main(void)
{char str[] = "abc";Combination(str);return 0;
}

由于组合可以是1个字符的组合,2个字符的字符……一直到n个字符的组合,因此在函数void Combination(char* string),我们需要一个for循环。另外,我们用一个vector来存放选择放进组合里的字符。
方法二:用位运算来实现求组合

#include<iostream>
using namespace std;int a[] = {1,3,5,4,6};
char str[] = "abcde";void print_subset(int n , int s)
{printf("{");for(int i = 0 ; i < n ; ++i){if( s&(1<<i) )         // 判断s的二进制中哪些位为1,即代表取某一位printf("%c ",str[i]);   //或者a[i]}printf("}\n");
}void subset(int n)
{for(int i= 0 ; i < (1<<n) ; ++i){print_subset(n,i);}
}int main(void)
{subset(5);return 0;
}

字符串全排列扩展----八皇后问题题目:在8×8的国际象棋上摆放八个皇后,使其不能相互攻击,即任意两个皇后不得处在同一行、同一列或者同一对角斜线上。下图中的每个黑色格子表示一个皇后,这就是一种符合条件的摆放方法。请求出总共有多少种摆法。这就是有名的八皇后问题。解决这个问题通常需要用递归,而递归对编程能力的要求比较高。因此有不少面试官青睐这个题目,用来考察应聘者的分析复杂问题的能力以及编程的能力。由于八个皇后的任意两个不能处在同一行,那么这肯定是每一个皇后占据一行。于是我们可以定义一个数组ColumnIndex[8],数组中第i个数字表示位于第i行的皇后的列号。先把ColumnIndex的八个数字分别用0-7初始化,接下来我们要做的事情就是对数组ColumnIndex做全排列。由于我们是用不同的数字初始化数组中的数字,因此任意两个皇后肯定不同列。我们只需要判断得到的每一个排列对应的八个皇后是不是在同一对角斜线上,也就是数组的两个下标i和j,是不是i-j==ColumnIndex[i]-Column[j]或者j-i==ColumnIndex[i]-ColumnIndex[j]。关于排列的详细讨论,详见上面的讲解。
接下来就是写代码了。思路想清楚之后,编码并不是很难的事情。下面是一段参考代码:

#include<iostream>
using namespace std;int g_number = 0;
void Permutation(int * , int  , int );
void Print(int * , int );void EightQueen( )
{const int queens = 8;int ColumnIndex[queens];for(int i = 0 ; i < queens ; ++i)ColumnIndex[i] = i;    //初始化Permutation(ColumnIndex , queens , 0);
}bool Check(int ColumnIndex[] , int length)
{int i,j;for(i = 0 ; i < length; ++i){for(j = i + 1 ; j < length; ++j){if( i - j == ColumnIndex[i] - ColumnIndex[j] || j - i == ColumnIndex[i] - ColumnIndex[j])   //在正、副对角线上return false;}}return true;
}
void Permutation(int ColumnIndex[] , int length , int index)
{if(index == length){if( Check(ColumnIndex , length) )   //检测棋盘当前的状态是否合法{++g_number;Print(ColumnIndex , length);}}else{for(int i = index ; i < length; ++i)   //全排列{swap(ColumnIndex[index] , ColumnIndex[i]);Permutation(ColumnIndex , length , index + 1);swap(ColumnIndex[index] , ColumnIndex[i]);}}
}void Print(int ColumnIndex[] , int length)
{printf("%d\n",g_number);for(int i = 0 ; i < length; ++i)printf("%d ",ColumnIndex[i]);printf("\n");
}int main(void)
{EightQueen();return 0;
}

转载:http://zhedahht.blog.163.co

题目:输入两个整数n和m,从数列1,2,3...n中随意取几个数,使其和等于m,要求列出所有的组合。

#include <iostream>
#include <list>
using namespace std;
list<int> list1;
void find_factor(int sum,int n)
{//递归出口if(n<=0||sum<=0)return;//输出找到的数if(sum==n){list1.reverse();for(list<int>::iterator iter=list1.begin();iter!=list1.end();iter++)cout<<*iter<<"+";cout<<n<<endl;list1.reverse();}list1.push_front(n);find_factor(sum-n,n-1);//n放在里面list1.pop_front();find_factor(sum,n-1);//n不放在里面
}int main(void)
{int sum,n;cin>>sum>>n;cout<<"所有可能的序列,如下:"<<endl;find_factor(sum,n);return 0;
}




本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/259346.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式基于C#的工程化实现及扩展

设计模式基于C#的工程化实现及扩展 转载于:https://www.cnblogs.com/gzmg/p/3344833.html

Python实现atm机的功能

主要还是参考网上内容&#xff0c;自己做了修改。虽然代码有小bug&#xff0c;但是不影响学习和测试。功能&#xff1a;1.额度&#xff1a;80002.可以提现&#xff0c;手续费5%3.每月最后一天出账单&#xff0c;写入文件4.记录每月日常消费流水5.提供还款接口1.atm的脚本[rootp…

Direct ByteBuffer学习

ByteBuffer有两种一种是heap ByteBuffer,该类对象分配在JVM的堆内存里面&#xff0c;直接由Java虚拟机负责垃圾回收&#xff0c;一种是direct ByteBuffer是通过jni在虚拟机外内存中分配的。通过jmap无法查看该快内存的使用情况。只能通过top来看它的内存使用情况。 JVM堆内存大…

魔兽争霸Ⅲ运行时不能初始化directX的错误解决

运行魔兽争霸3不能初始化DirectX错误这样解决&#xff1a; 1&#xff1a;在运行中输入(winr)&#xff1a;dxdiag&#xff0c;查看显示栏&#xff0c;确定电脑已安装好directx 8.1以上&#xff0c;且下面的三个加速都已开启。 2&#xff1a;如果没有安装directx就下载安装一个&a…

Android7.0占用空间,Android7.0 开发者注意事项

1、当设备处于充电状态且屏幕已关闭一定时间后&#xff0c;设备会进入低电耗模式并应用第一部分限制&#xff1a;关闭应用网络访问、推迟作业和同步。如果进入低电耗模式后设备处于静止状态达到一定时间&#xff0c;系统则会对 PowerManager.WakeLock、AlarmManager 闹铃、GPS …

Android探索之旅 | 面向对象和Java基础

-- 作者 谢恩铭 转载请注明出处 上一篇 Android探索之旅 | Android简介 中说到&#xff1a; "Android的默认开发语言是Java&#xff0c;入门简单。而且&#xff0c;你的Java水平不需要多好就可以上手开发Android App了。" 不少朋友说看到后很是心安。 不过小编也不想…

DataGirdView 编辑项时的验证

dgvConfig.DataSource CreateTable();dgvConfig.Columns["编号"].ReadOnly true; //只读dgvConfig.AllowUserToAddRows false; //不允许添加新行dgvConfig.EditingControlShowing new DataGridViewEditingControlShowingEventHandler(dgvConfig_EditingControlS…

使用Vitamio打造自己的Android万能播放器(7)——在线播放(下载视频)

前言 本章将实现非常实用的功能——下载在线视频。涉及到多线程、线程更新UI等技术&#xff0c;还需思考产品的设计&#xff0c;如何将新加的功能更好的融入到现有的产品中&#xff0c;并不是简单的加一个界面就行了&#xff0c;欢迎大家交流产品设计和技术细节实现&#xff01…

生成0到1之间随机数的C代码

#include <stdlib.h>#include <stdio.h>#include <time.h>int main(){srand((unsigned)time(NULL));int i;double r;for(i0;i<50;i){r(float)rand()/RAND_MAX; printf("%f\n",r);}return 0;}

HTML声明文档类型后样式出错,doctype如何声明

如何doctype声明&#xff0c;新增的结构元素和功能元素HTML5已形成了最终的标准&#xff0c;概括来讲&#xff0c;它主要是关于图像&#xff0c;位置&#xff0c;存储&#xff0c;多任务等功能的增加。 新增的元素有绘画 canvas &#xff0c;用于媒介回放的 video 和 audio 元素…

Error-Project facet Java version 1.8 is not supported

最近导入最新的Strtus2.5.10.1 Demo时出现了这个错误 解决方案如下&#xff1a; 选中工程——右键——Properties 然后依次展开找到如图所示内容&#xff0c;将1.8改成1.7即可。 原因&#xff1a;工程默认配置是1.8&#xff0c;而本地环境JDK版本为1.7&#xff0c;两则不匹配造…

6.2

转载于:https://www.cnblogs.com/tutuaixiaomei/p/3354356.html

Tomcat全攻略

内容&#xff1a; 一&#xff1a;简单介绍二&#xff1a;安装及配置三&#xff1a;应用四&#xff1a;综述參考资料关于作者宗 锋西北大学计算机系硕士2001 年 12 月 随着java的流行&#xff0c;其在web上的应用也越来越广&#xff0c;tomcat作为一个开源的servlet容器&#xf…

《G档案》中关于游戏程序设计的文章

刚拿到前导的《G档案》&#xff0c;发现了主程刘刚的文章&#xff0c;是目前我所见 到的关于游戏编程的最好的一篇&#xff0c;与大家共享。转载&#xff1a;http://www.360doc.cn/article/2778_53476.html PC游戏编程 目录 1 游戏程序理论 1.1 技术基础 1.2 游戏底层 1.3 编…

shell笔记

system 磁盘 磁盘空间使用情况df查看文件或目录大小du挂载usb sudo fdisk -l # Find what the drive is called e.g. /dev/sdb1 sudo mkdir /media/usb sudo mount /dev/sdb1 /media/usb sudo umount /media/usb# umount sudo umount /media/usb utils awk 打印文件的第一列(域…

html5编辑文档,HTML5带各种趣味动画的文本编辑器

CSS语言&#xff1a;CSSSCSS确定body {background-color: #eee;}html,body {margin: 0px;height: 100%;overflow: hidden;}.toolbar {width: 100%;background: #fff;padding: 4px 10px;}.characters {display: inline-block;margin-right: 20px;vertical-align: top;}.characte…

社会转型

转载&#xff0c;版权由作者所有。 常常在政府工作报告中看到关于“社会转型期”的说法&#xff0c;不是太明白&#xff0c;在百度里找了找&#xff0c;果然有不少&#xff0c;摘抄下来&#xff0c;做为学习资料用&#xff1a; 一是指体制转型&#xff0c;即从计划经济体制向市…

在WPF中处理Windows消息

在Winform中 处理Windows消息通过重写WndProc方法 在WPF中 使用的是System.Windows. Sytem.Windows.Controls等名字空间&#xff0c;没有WndProc函数 WPF中处理消息首先要获取窗口句柄&#xff0c;创建HwndSource对象 通过HwndSource对象添加消息处理回调函数。 此外 WPF中没有…

Android Material风格的应用(三)--DrawerLayout

添加抽屉导航 Android Material风格的应用(一)--AppBar TabLayoutAndroid Material风格的应用(二)--RecyclerViewAndroid Material风格的应用(三)--DrawerLayoutAndroid Material风格的应用(四)--FloatActionButtonAndroid Material风格的应用(五)--CollapsingToolbar DrawerLa…

html5 数据缓存,HTML5: 本地缓存

HTML5 提供了两种在客户端存储数据的新对象&#xff1a;localStorage&#xff1a;没有时间限制的数据存储&#xff0c;在同一个浏览器中&#xff0c;只要没被手动清理&#xff0c;第二天、第二周或下一年之后&#xff0c;数据依然可用。sessionStorage&#xff1a;针对一个 ses…