-
C(n,m)
从n个字符中选取m个字符,获得所有的组合
用编程实现C(n,m)组合问题,可以用递归的方法的解决,将n个字符排列成一条流水线,然后从第一个字符开始选取,并且将已经选取的字符进行保存,如果已经选取了m个字符,那么就获得了一种组合结果,此时问题还没有解决,应该将刚刚保存的最后一个字符拿出来,然后选择流水线上的下一个字符,如果已经访问到了流水线的最后一个字符而选取的字符还没有达到m个,说明当前保存的字符无法得到一种组合,则将最后保存的字符拿出来,选择下一个字符。下图实现a,b,c,d四个字符三三组合来详细说明递归的过程:
递归的代码实现如下图,参数str作为流水线在递归的过程中保持不变;nLen在递归的过程中用来控制已经保存的字符数(初始值为m); 动态数组ve用来在递归的过程中记录已经访问到的字符;动态数组result用来保存最终的组合结果,每当nLen减小到0时,将ve中的字符全都读取出来并且保存到result中。
/*
* str: 源字符串
* nLen: 组合长度
* vector<char>: 用来记录当前已经访问过的字符
* vector<string>: 用来记录组合后的结果,通过size()可以获得最终的组合结果
*/
void perm(char* str, int nLen, vector<char>& ve, vector<string>& result)
{
if (nLen == 0) //已经找到一个全排列的组合,而且这个组合就在ve中
{
string st;
vector<char>::iterator it = ve.begin();
for (; it != ve.end(); it++)
{
st += *it;
}
result.push_back(st); //找到了一种组合,将它保存在动态数组中
return;
}
if (*str == '\0')
return;
ve.push_back(*str);
perm(str + 1, nLen - 1, ve,result);
//递归从这里出来的条件只有两种,
//要么str为空了, 说明当前已经遍历一遍字符串,
//要么nLen为0了,说明已经找到了一种组合
ve.pop_back(); //此时要将最后一个字符出栈,然后遍历下一个字符
perm(str + 1, nLen, ve, result);
}
函数Sectet用来检查当前输入参数是否能够形成排列
当传入字符串为空或者n小于m时,返回一个空表
/*
* str: n个字符组成的字符串
* nLen: 组合长度m
*/
vector<string> Secret(char* str, int nLen)
{
vector<string> strVect;
if (str == NULL || nLen > strlen(str)) //返回一个空表
return strVect;
vector<char> chVect;
perm(str, nLen, chVect, strVect);
return strVect;
}
下面为完整代码
#include<iostream>
#include<vector>
#include<string>
using namespace std;
vector<string> Secret(char* str, int nLen);
void perm(char* str, int nLen, vector<char>& ve, vector<string>& result);
int main()
{
char s[] = "abcd";
int n = strlen(s);
int m = 3;
vector<string> strVect = Secret(s, 3);
int nCount = strVect.size();
if (nCount == 0)
{
cout << "当前输入参数不符合排列规则!" << endl;
}
else
{
char buff[100];
sprintf_s(buff, "C(%d,%d)一共有%d种组合", n, m, nCount);
cout << buff << endl;
vector<string>::iterator it = strVect.begin();
for (; it != strVect.end(); it++)
{
cout << *it << " ";
}
}
return 0;
}
/*
* str: n个字符组成的字符串
* nLen: 组合长度m
*/
vector<string> Secret(char* str, int nLen)
{
vector<string> strVect;
if (str == NULL || nLen > strlen(str)) //返回一个空表
return strVect;
vector<char> chVect;
perm(str, nLen, chVect, strVect);
return strVect;
}
/*
* str: 源字符串
* nLen: 组合长度
* vector<char>: 用来记录当前已经访问过的字符
* vector<string>: 用来记录组合后的结果,通过size()可以获得最终的组合结果
*/
void perm(char* str, int nLen, vector<char>& ve, vector<string>& result)
{
if (nLen == 0) //已经找到一个全排列的组合,而且这个组合就在ve中
{
string st;
vector<char>::iterator it = ve.begin();
for (; it != ve.end(); it++)
{
st += *it;
}
result.push_back(st); //找到了一种组合,将它保存在动态数组中
return;
}
if (*str == '\0')
return;
ve.push_back(*str);
perm(str + 1, nLen - 1, ve,result);
//递归从这里出来的条件只有两种,
//要么str为空了, 说明当前已经遍历一遍字符串,
//要么nLen为0了,说明已经找到了一种组合
ve.pop_back(); //此时要将最后一个字符出栈,然后遍历下一个字符
perm(str + 1, nLen, ve, result);
}
输出结果如下:
-
A(n,n)
全排列算法
/*
* str: 由n个字符组成的字符串
* start: 当前遍历到的字符在字符串中的位置
* end: 字符个数
* permVect: 保存全排列的结果的动态数组
*/
void Aperm(char* str, int start, int end, vector<string>& permVect)
{
//得到全排列的一种情况,将它保存在数组中
if (start == end) {
string s = str;
permVect.push_back(s);
return;
}
for (int i = start; i < end; i++) {
swap(str[start], str[i]); //交换
Aperm(str, start + 1, end, permVect);
swap(str[i], str[start]); //回溯
}
}
-
A(n,m)
由公式A(n,m)=C(n,m)*A(m,m)可以将排列问题转换为组合问题和全排列问题,先得到C(n,m)的所有集合,然后对集合中的每一个字符串进行全排列,就得到了排列问题的解;
#include<iostream>
#include<vector>
#include<string>
using namespace std;
/*
* 排列组合公式
* A(nm)=C(nm)*A(mm);
*/
vector<string> Secret(char* str, int nLen);
void Cperm(char* str, int nLen, vector<char>& ve, vector<string>& result);
void Aperm(char* str, int start, int end, vector<string>& permVect); //计算全排列A(mm)的公式
int main()
{
char s[] = "abcdefg";
int n = strlen(s);
int m = 4;
vector<string> strVect = Secret(s, m);
int nCount = strVect.size();
if (nCount == 0)
{
cout << "当前输入参数不符合排列规则!" << endl;
}
else
{
char buff[100];
sprintf_s(buff, "A(%d,%d)一共有%d种组合", n, m, nCount);
cout << buff << endl;
vector<string>::iterator it = strVect.begin();
for (; it != strVect.end(); it++)
{
cout << *it << " ";
}
}
return 0;
}
/*
* str: 由n个字符组成的字符串
* start: 当前遍历到的字符在字符串中的位置
* end: 字符个数
* permVect: 保存全排列的结果的动态数组
*/
void Aperm(char* str, int start, int end, vector<string>& permVect)
{
//得到全排列的一种情况,将它保存在数组中
if (start == end) {
string s = str;
permVect.push_back(s);
return;
}
for (int i = start; i < end; i++) {
swap(str[start], str[i]); //交换
Aperm(str, start + 1, end, permVect);
swap(str[i], str[start]); //回溯
}
}
vector<string> Secret(char* str, int nLen)
{
vector<string> strCVect; //保存所有的C(m,n)排列结果
vector<string> strAVect; //保存所有的A(m,n)排列结果
if (str == NULL || nLen > strlen(str)) //返回一个空表
return strCVect;
vector<char> chVect;
Cperm(str, nLen, chVect, strCVect);
//拿出每一种结果进行组合C(nm)计算
for (vector<string>::iterator it = strCVect.begin(); it != strCVect.end(); it++)
{
string s = *it;
char* str = const_cast<char*>(s.c_str());
Aperm(str, 0, nLen, strAVect); //计算A(mm),得到nLen个字符的所有全排列结果
}
return strAVect;
}
/*
* str: 源字符串
* nLen: 组合长度
* vector<char>: 用来记录当前已经访问过的字符
* vector<string>: 用来记录组合后的结果,通过size()可以获得最终的组合结果
*
*/
void Cperm(char* str, int nLen, vector<char>& ve, vector<string>& result)
{
if (nLen == 0) //已经找到一个全排列的组合,而且这个组合就在ve中
{
string st;
vector<char>::iterator it = ve.begin();
for (; it != ve.end(); it++)
{
st += *it;
}
result.push_back(st); //找到了一种组合,将它保存在动态数组中
return;
}
if (*str == '\0')
return;
ve.push_back(*str);
Cperm(str + 1, nLen - 1, ve, result);
//递归从这里出来的条件只有两种,
//要么str为空了, 说明当前已经遍历一遍字符串,
//要么nLen为0了,说明已经找到了一种组合
ve.pop_back(); //此时要将最后一个字符出栈,然后遍历下一个字符
Cperm(str + 1, nLen, ve, result);
}