n个元素中取m个元素的组合
如A{1,2,3}则有这些组合:1,2,3,12,13,23,123;
我们可以把问题分解如下:
1)求数组中由1到n个元素的组合f(n,m) (m>=1 && m<=n;n为数组元素个数);
2)对于f(n,m),我们从数组中任意取一个元素,然后再从剩下的n-1个元素中取m-1个元素,既f(n-1,m-1);
3)重复第2步,直到f(n-m+1,1),即从n-m+1个元素中取出最后一个元素;
4)把有效的组合压栈,并在压栈前判断该组合在栈中是否存在,避免由于数组元素的重复而导致组合重复。
<script language="javascript" type="text/javascript">
var result = new Array(); //保存所有组合的数组
function getAllComb(myarr)
{
var len=myarr.length;
for(var i=1;i<=len;i++)
getComb(myarr,len,i);
document.write("数组("+myarr.join(",")+")的所有的组合(共"+ result.length+"种)如下:<hr>"+result.join("\t"));
}
//从数组myarr(n)中任选m个元素的所有组合(m>=1 && m<=n)。
function getComb(myarr,n,m,rs)
{
if(rs==null)
rs = new Array();
for(var i=n;i>=m;i–)
{
rs[m-1]=myarr[i-1]; //取出第n个元素作为组合的第一个元素
if(m>1)
getComb(myarr,i-1,m-1,rs); //递归,在n-1个元素中取m-1个元素,直到取出最后一个元素
var comb = rs.join(""); //获得一个组合
if(!checkExist(result,comb))
result.push(comb);
}
}
//查找某元素是否存在数组中,存在返回true,不存在返回false
function checkExist(myarr,e)
{
for(var i=0;i<myarr.length;i++)
if(e==myarr[i]) return true;
return false;
}
//测试
var arr=new Array(1,2,3,3,4,5);
getAllComb(arr);
输出结果:
数组(1,2,3,3,4,5)的所有的组合(共47种)如下:
5 4 3 2 1 45 35 25 15 34 24 14 33 23 13 12 345 245 145 335 235 135 125 334 234 134 124 233 133 123 3345 2345 1345 1245 2335 1335 1235 2334 1334 1234 1233 23345 13345 12345 12335 12334 123345
深度优先算法求含有N个元素的集合的全部组合
先来看另外一道题:给定整数:a1, a2, a3.....an, 判断是否可以从中选出任意个数,使其和等于K, (数字的个数,取1--N个数都可以),这道题要求找出这N个数中选1,2,3...N个元素的所有组合,如果任何一个组合满足和为K, 就找到了答案,
所以:本质上,这道题就是要求出这个集合的所有的组合,怎么求所有的组合?
我的理解:对任何元素a 属于A集合, 求子问题1 (包含这个元素时的组合),再加上子问题 2(不包含这个元素的组合),子问题1和子问题2本质上又是和包含这两个子问题的父问题本质上是一样的,所以用递归可以解决:
#include <string>
#include <iostream>
using namespace std;
char* arry[] = {"a", "b", "c", "d", "e"};
void DfsCombination(int n, string& out){
if (n == sizeof(arry)/sizeof(char *)){
if(!out.empty())
cout << out <<endl;
return;
}
// 不加上a[i]的情况
DfsCombination(n + 1, out);
// 加上a[i]的情况
DfsCombination(n + 1, out + arry[n]);
}
int _tmain(int argc, _TCHAR* argv[])
{
string output;
DfsCombination(0, output);
system("pause");
return 0;
}
递归实现组合数打印,C(n,m),从n个数中选出m个数(m<=n)个的全部组合打印。
先看一个例子:
C(5,3) = 10
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
大家注意到没有,
1 | 2 3
1 | 2 4
1 | 2 5
1 | 3 4
1 | 3 5
1 | 4 5
------ C(4, 2)∵可以在{2, 3, 4, 5}中挑2个出来。
2 | 3 4
2 | 3 5
2 | 4 5
------ C(3, 2)∵可以在{3, 4, 5}中挑2个出来。
3 | 4 5
------ C(2, 2)∵只能在{4, 5}中挑2个出来。
这样就很容易写出递归算法来。
Algorithm combination(n, k, A[l..n+l-1])
if k = 0
print ary[1..k]
else
for i←1 to n-k+1
ary[index++] = A[l+i-1]
combination(n-i, k-1, A[l+i..n+l-1])
--index
endfor
大家可能会疑惑干嘛要弄出个index,还有一加一减的(你手工算一下就知道了)。
可以在pku acm 2245 Lotto 上试一试。个数中选出m个数(m<=n)个的全部组合打印。
int *dst_array,top=0;//中间数组,存放中间求解过程,count计数所有的组合个数
int cnt = 0;
//打印长度为n的数组元素
static void printA(int*parray,int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%d ",parray[i]);
}
}
//递归打印组合数
static void print_combine(int *pArray,int n,int m)
{
if(n < m || m==0)
{
return ;//情况一:不符合条件,返回
}
print_combine(pArray+1,n-1,m);//情况二:不包含当前元素的所有的组合
dst_array[top++]=pArray[0];//情况三:包含当前元素
if(m==1)
{ //情况三-1:截止到当前元素
printA(dst_array,top);
printf("\n");
cnt++;
top--;
return;
}
print_combine(pArray+1,n-1,m-1);//情况三-2:包含当前元素但尚未截止
top--;//返回前恢复top值
}
int main()
{
int n,m,*parray;//存放数据的数组,及n和m
printf("---以下实现从n个数中选出m个数的全组合打印(n个数为1,2,3....n---\n");
printf("---请输入n 和m \n---");
scanf("%d%d",&n,&m);
printf("\n---以下是输出结果---\n");
parray=(int *)malloc(sizeof(int)*n);
dst_array=(int *)malloc(sizeof(int)*m);
int i;
for(i=0;i<n;i++)
{
//初始化数组
//scanf("%d",&parray[i]);
parray[i] = i+1;
}
print_combine(parray,n,m);//求数组中所有数的组合
printf("=====C(%d,%d)共计:%d个=====",n,m,cnt);
free(parray);
free(dst_array);
return 0;
}
n个元素中取m个元素的全排列
全排列的要求:
输入:字符串"abc"。
输出:如下图示,
思路1——全排列的递归实现核心思想:
比如对于字符串”abc”,
第一步:求所有可能出现在第一个位置的字符即:a,b,c。
使用方法:把第一个字符和后面的b、c字符进行交换。
第二步:把第一个字符后面的所有字符仍然看成两部分,即后面的第一个字符及除此之外的其他字符。然后完成后面的第一个字符与其他字符的交换。比如:第2个位置的b与第3个位置c的交换。
第三步:依次递归,直到末尾的’\0’为止。
全排列的递归实现:
static int g_sCnt= 0;
//permutation的重载版本.
voidpermutation(char* pStr, char* pBegin)
{
if(*pBegin == '\0')
{
++g_sCnt;
cout << pStr << endl;
}
else
{
for(char* pCh = pBegin; *pCh != '\0'; ++pCh)
{
//从第一个字符依次和后面的字符进行交换.
char temp = *pCh;
*pCh = *pBegin;
*pBegin = temp;
permutation(pStr,pBegin+1);
//交换回原样,以便再递归处理后面的字符.
temp = *pCh;
*pCh = *pBegin;
*pBegin = temp;
}//end for
}//end else
}
int main()
{
char strSrc[] = "abcd";
permutation(strSrc,strSrc);
cout<< "共 " << g_sCnt << " 种排列!" <<endl;
return 0;
}
思路2——全排列的STL实现:
有时候递归的效率使得我们不得不考虑除此之外的其他实现,很多把递归算法转换到非递归形式的算法是比较难的,这个时候我们不要忘记了标准模板库STL已经实现的那些算法,这让我们非常轻松。
STL有一个函数next_permutation(),它的作用是如果对于一个序列,存在按照字典排序后这个排列的下一个排列,那么就返回true且产生这个排列,否则返回false。
注意,为了产生全排列,这个序列要是有序的,也就是说要调用一次sort。
实现很简单,我们看一下代码:
void permutation(char* str)
{
int length = strlen(str);
//第1步:排序
sort(str,str+length);
//第2步:调用函数next_permutation
do
{
for(int i=0; i<length; i++)
{
cout<<str[i];
}
cout << endl;
}while(next_permutation(str,str+length));
}
int main()
{
char str[] = "acb";
permutation(str);
return 0;
}
思路3:全排列的字典树实现