RoyDeng's Weblog

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  4 随笔 :: -1 文章 :: 29 评论 :: 1 引用

上次我在《基于N层满N叉树的排列算法》 这篇文章给大家演示了如何从一个N层满N叉树得出一个N全排列。N层满N叉树确实是一个蛮奇妙的数据结构,它又叫做完美N叉树,利用它同样可以形成N个元素的组合。

我们知道N个元素的M组合的计算公式为,Cnm = n!/(m!*(n-m)!)

例如4个元素中取3个进行组合的组合数=4/(2*1)=4,我们这次也试着从一个完美4叉树中获得它的3组合。



图一是一个完美4叉树,由于树结点太多,这里的b1,b2,b3,b4节点的子数采用的是简略方式表示(三角形表示其子树节点集合,例如图二就列出了b1的子树)。请注意,这次的完美4叉树跟 《基于N层满N叉树的排列算法》 一文中的有所不同。这次的树结点所代表的数字是分层的,也就是,顶层节点为0,b1、b2、b3、b4这四个次一层节点为1,如此下去,接着两层的节点数字分别等于其节点所在的层数。这种安排是为了组合算法而特意设置的。

好了,接下我们用深度优先的搜索策略来逐个的从这个树中找出所有的3组合。

首先看图二:



(1)首先我们获得a, b1,c1-1这三个节点。由于是3组合,所有搜索到c1-1可以返回,这时可以输出结果[0,1,2];
(2)搜索点回到b1后继续转向b1的子节点c1-2。由于c1-1已经在之前被选过,于是,我们跳过c1-2(以避免产生重复的组合),直接进入d1-2-1。由于d1-2-1能形成一个新的组合,所以d1-2-1获选。于是沿着路径[a][b1][c1-2][d1-2-1],我们有三个选中的节点(绿色表示选中,灰色表示落选,粗蓝线表示结果的路径)。于是,我们输出第二个结果[0,1,3];
(3)由于d1-2-1已经是叶结点,也就是说在b1有效的情形下,已经不可能存在另外一条路经,使得新的组合可以产生出来(请思考一下,为什么),于是我们可以把b1的颜色变为灰色,也就是说,将来的搜索不再选则b1了。如图三所示。



(4)我们的深度优先仍继续进行下去,这次到达了b1的第3个儿子c1-3。这次的情形和步骤(2)不同,步骤(2)的情形是b1被选中,而这次则不然。所以c1-3获选可以形成新的组合。于是c1-3获选。
(5)c1-3的儿子d1-3-1如同步骤(3)一样被选中。于是一个新的组合可以输出[0,2,3];
(6)在c1-4,情形同c1-2相象,所以c1-4也是落选的。不过这次已经不可能通过c1-4的子树形成新的组合了,因为已经没有足有的节点形成3组合。至此b1的所有子树搜索完毕。
(7)在这里我们先停顿一下。说回b1,这次搜索是a和b1最初被选中的条件下展开的,并且b1被设置为灰色(落选)的情形也已经处理过,接下来就将要在b2展开新的搜索。问题是如果b2的搜索同b1没有什么区别的话,我们只会得到跟b1一样的结果,这时我们所不愿意见到的。为此,我们需要改变一下进入b2搜索的入口条件——将a变成灰色(剔除),于是,我们能在一个全新的上下文(contxet)中展开新一轮的搜索(如图四所示)。



(8)这次我们很快的得到[b2][c2-1][d2-1-1]这条路经。于是输出[1,2,3]
(9)由于d2-1-1是叶结点,并且在b2为绿色(已选)的情形下,b2的子树中已经不再可能存在新的组合可供。于是b2以及它的子树的搜索完毕。
(10)对于b3,b4的情形,无论b3,b4处于什么的状态,都不可能获得新的输出组合了(请想想为什么)。于是全部搜索完毕。

我们恰好得到了期望中的4个组合输出。

这时一个非常高效的组合算法,处理得当的话能用o(1)的算法复杂性输出每一个组合。下图是性能测试的结果
在10组合3的情形下每秒输出组合数为9,401,760个,并且随着组合规模的扩大,性能丝毫不受影响,我想这是本算法的一个比较有特色的地方了(绝对的线性)。



下图是演示程序所形成的字符串5组合3


两个对称组合[20 C 5]和[20 C 15]所形成的互补花纹


我觉得能领会了组合数学和图论之间这这种巧妙联系,是研究这些算法的最大得益。学好图论,真的非常有帮助阿。

从这里下载包括测试演示程序的整个项目文件(VS2008版本的)。版权?Copy Free,不过风险自担
http://files.cnblogs.com/GlobalFamily/Combination.rar

对组合算法有兴趣的朋友可以参考CodeProject上的同类文章
http://www.codeproject.com/KB/recipes/combinations.aspx


做人要厚道,转载请注明出处。
作者Blog RoyDeng

posted on 2008-02-26 22:45 RoyDeng 阅读(2177) 评论(8)  编辑 收藏 网摘

评论

#1楼  2008-02-27 12:54 毁于随      
怎么又发了一遍
  回复  引用  查看    

#2楼  2008-02-27 14:03 Silent Void      
算法有什么用?就是求组合吗?
记得有现成的o(1)算法能求每一个组合(m in n),只用了几个位操作,回头我找找看。。。

一个疑问:“10组合3的情形下每秒输出组合数为9,401,760个”,10组合3的话,也就那么百来个组合,咋输出这么多的?(不好意思,懒得看代码...)
  回复  引用  查看    

#3楼  2008-02-28 09:19 jillzhang      
@ffffffffffffffffffffffffffffffffffff
着兄弟也太阴了
  回复  引用  查看    

#4楼  2008-02-28 10:06 Silent Void      
大致思路如下:获取下一个具有同样数量的1位的更大的数
unsigned snoob(unsigned x)
{
unsigned smallest, ripple, ones;//e.g.: x=XXX0 1111 0000
smallest = x & -x; // 0000 0001 0000
ripple = x + smallest; // XXX1 0000 0000
ones = x ^ ripple; // 0001 1111 0000
ones = (ones >> 2) / smallest; // 0000 0000 0111
return ripple | ones; // XXX1 0000 0111
}
(http://www.cnblogs.com/happyhippy/archive/2007/04/24/725127.html,上面是在《Hacker's Delight》看到的一个算法)。
参考该算法,可以考虑将用位串表示集合,1表示选中,0表示未选中,从N个元素中选出M个元素,则转化为N位中有M个位为1:
最小:0…………01……1(N-M个0,M个1)
最大:1……10…………0(M个1,N-M个0)
用上面的位操作可以求得位于最小值和最大值之间M个1的组合。
如果N<=32,可以考虑用uint
如果32如果N>64,可以考虑用BitArray,不过BitArray只实现了按位的Or、And、Xor、Not等,+、>>、/等操作需要自己实现
  回复  引用  查看    

#5楼  2008-02-28 10:51 Silent Void      
汗,博客园的回复中,部分内容显示不出来,查看Source HTML没有问题。
如果N<=32,可以考虑用uint
如果N in (32,64],可以考虑用ulong
如果N>64,可以考虑用BitArray,......
  回复  引用  查看    

看的不是很明白!

1楼的好恶劣!
  回复  引用    

#7楼  2008-02-29 09:10 Silent Void      
@RoyDeng
怪了,你的回复在评论列表中显示不出来?
如果N>64,可以考虑用BitArray,......
  回复  引用  查看    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-02-29 23:52 编辑过
Google站内搜索
[推荐职位]上海盛大网络招聘架构师



China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
近千种 9-95 新二手计算图书火热销售中!
开发者征途系统新作:《设计模式——基于C#的工程化实现及扩展》

相关文章:

相关链接: