子集生成和组合问题

子集

一个包含n个元素的集合,它的子集(包含空集)的个数为\large 2^{n}个。

这样我们发现,子集的状况可以用二进制的概念进行对照是最直观的。

例如:n=3的集合{\large a_{0}\large a_{1}\large a_{2}},他的子集和二进制数的关系如下表;

子集\large a_{0}\large a_{1}\large a_{1}\large a_{0}\large a_{2}\large a_{2}\large a_{0}\large a_{2}\large a_{1}\large a_{2}\large a_{1}\large a_{0}
二进制数000001010011100101110111

可以看出,每个自己对应一个二进制数,这个二进制数中的每个1都对应着子集中某个元素,而且子集中元素是没有顺序的,这也可以解释为什么子集数量是\large 2^{n}个了。

下面程序模拟该过程打印出了所有子集:

#include<bits/stdc++.h>
using namespace std;
void subset(int n){
	for(int i=0;i<(1<<n);i++){    //1左移n位,为2的n次幂 
		for(int j=0;j<n;j++)		//打印一个子集,即打印i的二进制数中所有1 
		if(i&(1<<j)) cout<<j<<" ";	//从i的最低位开始拒不检查每一位,若为1,打印 
		cout<<endl;
	}	
}
int main(){
	int n;
	cin>>n;
	subset(n);  //生成n元素所有子集的子函数 
}

若打印n个数中任意m个组合,可以对以上方法进行稍微的修改,只要找到符合1个数的子集并打印出来,即为该问题的解。

所以问题转变为查找1的个数为m的二进制数,这些二进制数就是需要打印的子集。

若是逐位判断是否为1的话,则需要检查n次,另有一个更快的方法,他可以直接定位二进制数中1的位置。用到了一个神奇的操作——kk=kk&(kk-1),功能是消除kk的二进制数的最后一个1;连续进行这种操作,每次消除一个1,sum就统计一次,直到原数为0,sum的值就为该二进制数中1的个数。

例如:二进制数1011

1011&(1011-1)=1011&1010=1010;   sum=1;

1010&(1010-1)=1010&1001=1000;  sum=2;

1000&(1000-1)=1000&0111=0000;  sum=3;

end~

则该数里面有3个1。

再简述一下该操作:

(1)用kk=kk&(kk-1)清除kk最后一个1;

(2)sum++;

(3)继续上述操作,直到kk=0;

在树状数组中也有一个类似的操作——lowbit(x)=x&-x,功能是计算x的二进制数的最后一个1;

下面给出求n个数中任意m个数的组合的代码:

#include<bits/stdc++.h>
using namespace std;
void Set(int n,int m){
	for(int i=0;i<(1<<n);i++){
		int sum=0,kk=i;  //sum存放该二进制数多少个1 
		while(kk){    //kk&(kk-1)操作进行消除1 
			kk=kk&(kk-1);
			sum++;
		} 
		if(sum==m){   //如果是要找的集合,输出 
			for(int j=0;j<n;j++) 
			if(i&(1<<j)) cout<<j<<" ";
		}
		if(sum==m) cout<<endl;
	}
}
int main(){
	int n,m;
	cin>>n>>m;
	Set(n,m);  //n个数里任意m个数的集合 
} 

妈妈在也不怕我不会排列组合问题啦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值