暴力求解-枚举排列(二)

通过上一篇的内容,我们可以得到一颗递归解答树

假设n=4.序列为{1,2,3,4},可以用一颗树显示出了。这颗树和前面介绍的二叉树不同。第0层(根)结点有n个子结点,第1层结点各有n-1个子结点,第2层结点各有n-2个子结点,第3层结点各有n-3个子结点,....,第n层结点都没有子结点(即都是叶子),而每个叶子对应于一个排列,共有n!个叶子。由于这棵树展示的是从什么都没做逐步生成完整解的过程,因此将其称为解答树。

如果某问题的解可以由多个步骤得到,而每个步骤都有若干种选择(这些候选方案集可能会依赖于先前做出的选择),且可以用递归枚举法实现,则它的工作方式可以用解答树来描述。

这颗解答树一共有多少个结点呢?可以逐层查看,第0层有1个结点,第1层n个,第2层有n*(n-1)个结点(因为第1层的每个结点都有n-1结点),第3层有n(n-1)*(n-2)个(因为第2层的每个结点都有n-2个结点),....,第n层有n*(n-1)*(n-2)*.....*2*1 = n!个。

下面把它们加起来,为了推导方便,把n*(n-1)*(n-2)*...*(n-k)写成n! / (n-k-1)!,则所有结点之和为:

T(n) = \sum_{k=0}^{n-1} \frac{n!}{(n-k-1)!} = n!\sum_{k=0}^{n-1}\frac{1}{(n-k-1)!} = n!\sum_{k=0}^{n-1}\frac{1}{k!}

根据高等数学中的泰勒展开公式,\lim_{n->\infty }\sum_{k=0}^{n-1}\frac{1}{k!} = e,因此T(n) < n! e = O(n!) 。由于叶子有n!个,倒数第二层也有n!个结点,因此上米娜的各层全部加起来也不到n!,这是一个很重要的结论:在多数情况下,解答数上的结点集合全部来源于最后一两层。和它们相比,上米娜的结点数可以忽略不计。

不熟悉泰勒展开公式也没有关系,可以写一个程序,输出\sum_{k=0}^{n-1}\frac{1}{k!}随着n增大时的变化,并发现它能很快收敛。这就是计算机的优点之一-可以通过模拟避开数学推导。即使无法严密而精确的求解,也可以找到令人信服的实验数据。

下一个排列

枚举所有排列的另一个方法是慧聪字典序最小排列开始,不停调用 求下一个排列的过程。如何求下一个排列呢?C++的STL中提供了一个库函数 next_permutation。

运行结果:

void STL_print_repeat_permutation(int n,int *P)
{
   //从字典序的最小排列开始,不停调用求下一个排列的过程
   do{
      for(int i=0;i<n;i++) //输出排列P
        printf("%d ",P[i]);
      printf("\n");
   }while(next_permutation(P,P+n)); //求下一个排列
}

需啊哟注意的是,上述代码同样使用于可重集。记得在此之前对P数组进行排序。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值