包含重复元素的全组合

liangbch 2008-12-09 12:23:36
题目要求:一个数组中,包含n个元素,可能包含重复元素,这n个元素按照非递减顺序排列,求从这n个元素中取出m个元素的所有组合?
为了方便起见,定义程序接口如下:

int allCombo(int data[], int n, int m);

运行这个函数allCombo时,应该能够打印出所有组合,并返回组合的个数

例如:
data[]={2,2,3,3,5}
运行 allCombo(data,5,3) 应该能够显示下列组合。
{2,2,3}
{2,2,5}
{2,3,3}
{2,3,5}
{3,3,5}
...全文
487 点赞 收藏 28
写回复
28 条回复
切换为时间正序
请发表友善的回复…
发表回复
ringming 2008-12-17
帮顶了! 学习
回复
hityct1 2008-12-11
mark
回复
fenix124 2008-12-11
其实就是某楼说的DP,同时减少了点空间负责度
回复
fenix124 2008-12-11
//好歹也给出了个程序,给点分吧
//复杂度很低

#include <stdio.h>
#include <string.h>

#define MAXN 10000
int count[MAXN];
int countlen;

int rollA[MAXN+1];
int rollB[MAXN+1];

void initcount(int data[],int n)
{
int i,j,c;
c = 1;
for(i = 1,j = 0;i < n;i++)
{
if(data[i] != data[i-1])
{
count[j] = c;
j++;
c = 1;
}
else
{
c++;
}
}
count[j] = c;
countlen = j+1;
}

void getCombo(int ra[],int rb[],int m,int dep)
{
int i,j;
rb[0] = 1;
for(i = 1;i <= m;i++)
{
rb[i] = 0;
for(j = 0;j <= count[dep] && i-j >= 0;j++)
rb[i] += ra[i-j];
}
}

int allCombo(int data[], int n, int m)
{
int i;
initcount(data,n);
rollA[0] = 1;
for(i = 0;i < countlen;i++)
{
if(i%2 == 1)
{
getCombo(rollB,rollA,m,i);
}
else
{
getCombo(rollA,rollB,m,i);
}
}
if(i%2) return rollB[m];
else return rollA[m];
}

int main()
{
int data[]={2,2,3,3,5};
printf("%d\n",allCombo(data, 5, 3));
return 0;
}

回复
fenix124 2008-12-11
简单的说就是有N个数,每个数的数目不妨设为Y[i]。
计算sum(X[i]) = C(C为组合的大小),在限制X[i] <= Y[i]的解的个数

解法有很多,有DP,母函数法等。

回复
medie2005 2008-12-11
如果考虑输出,那就太没意思了。

我这里的意思仅仅是想精益求精罢了。

实质上,对生成组合模式而言,如果想要完美的效率,一般都要考虑Gray Code。

至于litaoye说的进位时的比较次数似乎并没有减少,这个依赖于实现,实质上,每次只需要一次加法,一次比较。如不考虑输出,效率上完全可以翻倍。
回复
绿色夹克衫 2008-12-11
看了一下gray code(格雷码),确实有他简单的地方,且在本题当中,丝毫不影响输出的结果,只是输出的顺序有些变化。

以3,3,2为例,gray code为:

000 001 011 010 020 021 121 120 110 111 101 100 200 201 211 210 220 221

虽然进位简单了,但进位时的比较次数似乎并没有减少。
回复
liangbch 2008-12-10
同意楼上,进位处理应该不是瓶颈。
回复
tailzhou 2008-12-10
1073741823大约是2^30;
进位的次数(2^30) 相对于 组合的输出的操作次数(30*2^30)来讲是很小的;

回复
medie2005 2008-12-10
呵呵,我来扩展一下吧。

对求n的所有因子的问题,如上所说,我们可以转化为一个混合进制的问题。
现在,我们来考虑实现技巧。

显然,如果直接模仿10进制的进位操作,那么,可能在得到下一个数的时候,需要多次进位。
比如,混合进制{3,3,2}。 那么,在这个混合进制下,对121来说,下一个数是200。可以看到,在得到200的过程中,进位发生了两次,同时也要比较三次。

如何使从上一个数到下一个数的操作尽量少?我们考虑Gray Code.
一般意义下的Gray Code是二进制的,它满足:上个码与下一个码的组成中仅仅只有一位发生变化。

对混合进制而言,是否也有这样的"Gray code"?如果有,如何构造?

如果解决了上面这两个问题,我们就有一个效率很完美的实现了。
回复
medie2005 2008-12-10
对小的情形来说,自然体现不出优势。
我们考虑从0自增到2^30的过程中,在二进制形式下,一共发生了多少次进位。
经计算,是1073741823次。
而使用Gray Code生成,我们就可以至少省掉这1073741823次进位。
回复
绿色夹克衫 2008-12-10
减少进位可以将较大的进位排在后面,比如{5,4,3,3,2}排成{2,3,3,4,5}

进位次数大概是2*3*3*4*1 + 2*3*3*2 + 2*3*3 + 2*4

整体复杂度应当不会翻倍,反正我也不是那么追求效率。

[Quote=引用 17 楼 medie2005 的回复:]
呵呵,我来扩展一下吧。

对求n的所有因子的问题,如上所说,我们可以转化为一个混合进制的问题。
现在,我们来考虑实现技巧。

显然,如果直接模仿10进制的进位操作,那么,可能在得到下一个数的时候,需要多次进位。
比如,混合进制{3,3,2}。 那么,在这个混合进制下,对121来说,下一个数是200。可以看到,在得到200的过程中,进位发生了两次,同时也要比较三次。

如何使从上一个数到下一个数的操作尽量少?我们…
[/Quote]
回复
tailzhou 2008-12-09
[Quote=引用 14 楼 medie2005 的回复:]
呵呵,{2,2,3,3,5}的混合进制应该是{3,3,2}。
因此,一个循环搞定.
[/Quote]

以前写的一个实现:
http://topic.csdn.net/u/20080630/19/11e24ad4-ffe3-4278-8091-aa1c3e598a95.html
回复
liangbch 2008-12-09
我在 那个 求一个数所有因数的帖子, 18楼给出完整代码。大家帮我看看,是否有改进的地方。

回复
medie2005 2008-12-09
呵呵,{2,2,3,3,5}的混合进制应该是{3,3,2}。
因此,一个循环搞定.
回复
medie2005 2008-12-09
这样的问题,说难也难,说简单也简单,liangbch如果有时间的话,可以看看taocp卷四,csdn上有,我上传过的。
回复
“因素”==>“因子”?

如果是求某个整数的所有因子,那是象7L说的,素数分解之后指直接将次数加一相乘就得出结果了。
回复
medie2005 2008-12-09
求因数不用这么复杂,是一个混合进制的问题。
以{2,2,3,3,5}为例,混合进制是{2,2,1},即:个位满1进1;十位满2进1;百位满2进1。
而n个元素中取出m个元素的多重组合,也是一个带约束的混合进制问题。
回复
liangbch 2008-12-09
那你这个程序怎么写,难道3个质因数就写一个3重循环,5个质因数就来个5重循环,这样的程序不通用。

当然,我也可以写一个通用的while()循环,可以处理任意层循环,这是其中的一种方法,枚举每个组合也是一种算法。所谓条条大路通罗马。


回复
回到这个问题本身。写了段代码,还有很大优化余地:

#include<iostream>
using namespace std;

bool nextcomb(int *data, int *p, int n, int m)
{
int last = n-1;
int i, j, k, temp;

i = last;
while(i>0 && !(p[i-1]==1 && p[i]==0))
i--;

if(i == 0)
return false;

p[i]=1;
p[i-1]=0;

if( data[i] == data[i-1] )
{
return nextcomb(data,p,n,m);
}
else
{
for(j=last, k=i+1; k<j; k++,j--)
{
temp = p[k];
p[k] = p[j];
p[j] = temp;
}
for(int h=0; h<n; h++)
{
if(p[h]==1) cout<<data[h]<<" ";
}
cout<<endl;
return true;
}
}

int allCombo(int data[], int n, int m)
{
int i;
int *p = new int[n];
for(i=0; i<m; i++)
{
p[i]=1;
cout<<data[i]<<" ";
}
cout<<endl;
for(i=m; i<n; i++)
p[i]=0;
int count = 1;
while( nextcomb(data,p,n,m) )
{
count++;
}
delete p;
return count;
}

int main()
{
int data[]={2,2,3,3,5};
cout<<"共有"<<allCombo(data, 5, 3)<<"种";
return 0;
}

回复
发动态
发帖子
数据结构与算法
创建于2007-08-27

3.2w+

社区成员

数据结构与算法相关内容讨论专区
申请成为版主
社区公告
暂无公告