递归问题与递归结构(二)

子集Subsets(组合)问题

        从n个数中任取k个数,问有多少种取法。(其中k ≤ n)

        这是典型的组合问题,对这种问题可以直接用现成的数学公式(m=(n-k+1)!/k!)来求得。如果要用递归来构建此问题的算法则可以作如下思考:

        设从n个数中取k个数组成数集记为g,则有,|g| = k;如果S表示所有的g的集合则有,S={g}。如果从n个数中任意指定一个数t,那么S = Sit ∪Sot,其中,Sit={g| t∈g},Sot={g| t不属于g}。设|Sit| = mit,|Sot| = mot,那么很明显有,|S| = m 且  m = mit + mot,其中 m=(n-k+1)!/k!。

        选取的t将所有的组合结果分为了两类,一类包含t,一类不包含t。对于包含t 的g组成的集合Sit,该如何求得其中的元素个数mit?因为每个元素g都包含了t,所以t 实际上对mit 的大小没有影响,影响mit大小的是g中去除了t 以后的所有元素的组合数。所以对mit 来说问题就变成了,求从n-1(去除t)个数中任选k-1(去除t)个数的组合值。找到递归结构。这一半的递归由于问题规模n会不断缩小,所选值也会不断缩小,因而其最终会落在Base Case k==0 中。 对于不包含t 的g组成的集合Sot,又该如何求得其中元素个数mot?由于每个元素g都不包含t,而要选的数仍为k个,所以对mot来说问题变成了,求从n-1个数中任选k个数的组合值。找到另一半的递归结构。这一半的递归由于问题规模不断缩小且k值不变,所以最终会落在Base Case k==n 上,因此算法为:

int  c(int n, int k)

{

     if (k==0 || k==n)

        return 1;

     else

        return  c(n-1, k) + c(n-1, k-1);

}  

   

       另一个典型的子集(组合)问题是,输入一个字符串比如“abcd”,假设字符串中所有字符可以任意组合,要求输出它们可能的所有的组合方式。对这一问题的思考方式与上面的相同,首先从中输入中任选一个字符,那么可以把所有的组合的字符串分为两类,包含此字符的与不包含此字符的。包含此字符的字符串的剩余部分的组合,要从剩余的3(n-1)个字符中产生,不包含此字符的字符串全部由剩余的3(n-1)个字符中产生。也就是要在剩余的字符中求所有满足要求的组合方式。递归结构找到。随着问题规模n的不断缩小,最后两者都会终止于Base Case n==0,因此有算法:

void  RecSubsets(string soFar, string rest)

{

     if ( rest == "")

         cout <<  soFar << endl;

     else

     {

           RecSubsets(soFar + rest[0], rest.Substr(1));    //rest[0] 表取字符串中第一个字符,rest.Substr(1)表返回字符串rest的一个子串,子串从rest的第二个字                                                                                               //符开始直到原字符串结束为止。

           RecSubsets(soFar, rest.Substr(1));

     }

}


需要注意的是,第一个组合问题与第二个组合问题略有不同。


还有比较经典的背包问题(knapsack filling)也属于子集(组合)问题。

一个有限容积的背包,n个有体积不一价值不同的物品,求算法,使背包中装的物品价值最大化。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值