圆排列问题 (注解)

<以前写的,转到这里>

摘自 王晓东的计算机算法设计与分析

===================
问题:
n个大小不一的圆,放到一个矩形中,并且矩形底边相切,当然圆不能重叠
形如:OoO....oOO ,求最小长度的圆排列。


算法
下列为已知条件:
r[i],i=1~n  该数组记录各圆半径  (注:初始化时,各值都是已知的)
x[i],i=1~n 该数组记录各圆圆心横坐标 (注:初始化0,  以后由程序计算才确立下来)
n     圆的个数     (注:初始化,已知的)
min 记录最小长度, (注:min初始化值蛮大的,设为100000)


class Circle
{
   定义了三个私有变量,*x,*r,n, min,
   定义一个友元函数CirclePerm(int n, float* a). //主要负责类Circle私有成员初始化}
   定义2个功能函数和一个递归函数:
        float Center(int t);// 计算第t个圆的圆心横坐标 。起点是?不急。
        void Compute(void); //计算圆排列长度


        void Backtrack(int t);//递归函数,从特征来看,属于回溯算法,笼统看,深度优先
}


--主要实现---------------------------
//求第t个圆的横坐标
float Circle:: Center(int t)
{
    float tmp=0;
    for(int j=1;j<1;j++)
      {
        float valuex=x[j]+2.0*sqrt(r[t]*r[j]);
        if(valuex>tmp) tmp=valuex;
      }
return  tmp;
}
----------------------
//计算圆排列长度
void Circle::Compute(void)
{
      float low =0;
     float  high=0;
     for(int i=1;i<=n;i++)
       {
          if(x[i]-r[i]          if(x[i]+r[i]>high) high=x[i]+r[i];
       }
     if(high-low}


-------------------------
void Circle::Backtrack(int t)
{
  if(t>n) Compute();
  else
       for(it j=t;j<=n;j++)
       {
            swap(r[t],r[j]);
            float centerx=Center(t);
            if(centerx+r[t]+r[1]                  {
                      x[t]=centerx;
                      Backtrack(t+1);
                  }
            swap(r[t],r[j]);
       }
}


======回溯算法 (该问题的解空间具有排列树的特性)==========
backtrack(int t)
{
   if(t>n) 计算排列长度
  else
   for(int j=t;j<=n;j++)
    {
        swap(r[t],r[j]);//确定当前第t个圆的半径,或者说,对第t个圆进行了选择
       求出第t个圆选入后,排列的长度,可称之为当前排列长度
       找到界限条件,如果满足条件
         {
            入选所选的第t个圆;
            开始深入到第t+1个圆,即backtrack(t+1)
         }
      swap(r[t],r[j]); //恢复现场
     }   
}


------如果能将上述伪码具体化,则到此止步--------------


 


 


========
如果不是自己实现,而是阅读代码,在了解框架的前提下,
只要关心两件事(这是回溯算法设计 必做的):
   1)  第t个元素入选,取值是什么。
   2)  找到限制条件,将第t个元素入选后造成的影响即限制条件 和现有可行解做一个比较
        满足条件,则继续递归。
========================================
1.看backtrack(int t)
   三个?:
  1)Compute()   
  2)Center()
  3)限制条件 centerx+r[t]+r[1]
原程序中,compute()依赖x[],而x[]由Center()来确定的。
所以先解读Center();


2.解读 center()
  王晓冬 点到即止,指得是他对全书程序中用到变量含义作了说明。对于大多数程序,的确够用了,但一些程序,还是略有不足的。
 
正题...
A。对于涉及参数是序数,个人作法:是取临界值,t取下界1,上界n,
     当t=1时,x[1]=0;这意味着,第一个圆心的横坐标被设为0, <----第一个有用信息
     当t=n时,因为涉及if之类看似很乱的东西,可以无视。


B。因为涉及开方sqrt, 所以第一个念头,2个圆心的距离求下看看。设两圆相切,半径分别为r1,r2,则计算后,得到结果为 2*sqrt(r1*r2)
好了,接下来,就得到一个朴素的想法:
  第t个圆的圆心横坐标=2*sqrt(r1*r2)+2*sqrt(r2*r3)+....


C.解决最后问题。里面if是怎么回事?
    =》
   圆1,圆2用到上面想法是正确的,问题出在后续的圆上,对于圆3,有2种可能:和圆2相切(朴素思想),另一种可能被 忽视的--和圆1 相切。
  而恰恰是圆1和圆3相切这种情况,这时计算得到数据是低估的(即小于实际正确值)。


也就是说,第t个圆很可能跟前面任何一个圆相切。这时,只要把各种情况得到取值全部算出,并把最大值 记录下来,就ok.


3.解读 computer()
   现在各圆横坐标x[],和圆半径r[]在手。
  考虑1。 受到center()启发,即大圆可能把众多小圆包含起来。所以上下边界很可能是由其中任何一个圆计算所得。
  考虑2。注意到前面横坐标是以第一个圆心为0起点的,所以长度就分成两部分。
 
  实际计算:
   0左部:low=x[i]-r[i]  这个值取最小值 ,才合题意。需要引入变量记录下来。
              形如oO 前面圆非常小
   0右部:high=x[i]+r[i] 这个值取最大值,才合题意 。需要引入变量记录下来。
             形如Oo  前面圆非常大


   结果就是:high-low
   残留问题:if(high-low    当然,由此可以确定的是 限制条件没办法逼出更优解。或者说,原程序提供的限制条件仅保证得到一个可行解。



4.最后一部分 限制条件确定(修正)

  .考虑放入t后,序列长度; 还要考虑继第t个球之后,余下的n-t个球放入后,对整个序列的长度的影响.
1)利用函数computer,把t个球的长度严格求出, 因为余下的n-t个球,可能对 此后长度 不产生影响(太小),或是产生影响,因为排列次序不明,这个贡献值很难计算.
    所以总长度变成: computer(t) + 一个数值(>=0) 
当前最优解是越小越好(题设要求) ,如果缩放?
    这样做: 缩放数<= 实际值(跟排列有关,很难计算)<当前最优解
=>,缩放数取computer(t), 这种情况下,放宽了条件,结果会导致可能解.
    ccomputer(t)<=computer(n)<当前最优解

 


2)另一种办法,因为t之后球贡献的长度没有办法计算,所以,可以对computer(t), 再次进行缩放.因为利用computer(t),在递归过程中,计算很多是重复的,程序会变得很不效率.
    由computer()可知, 它是通过求0右部, x[i]+r[i] ,0左部:x[i]-r[i]  由两者:确定并记录下最大长度. 画个图也很容易理解.   这时,我们取x[t]+r[t],0左部同时也就确定下来了,即r[1].
    就是说,用最后放入的t球,来确定整个长度, 即x[t]+r[t]+r[1], 这个值显然,有可能小于实际长度.
 ==>
     x[t]+r[t]+r[1] <=computer(t) <= computer(n)<当前最优解      you win!!!


---------------------------------------------------------------------------------
回溯法 最重要的就是求出界限函数.按问题性质,可画出子集树或排列树.
由此:我们可以获得了 对于排列树情形下,缩放何处理的手法:)


5.优化 了解1-4,优化就不难了。略。
   ~Fin~
  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值