排列组合问题:C(n,m)、A(n,n)、A(n,m)(基于c++实现)

  • 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);

}

 

猜你喜欢

转载自blog.csdn.net/mys1450663972/article/details/82588806