<以前写的,转到这里>
摘自 王晓东的计算机算法设计与分析
===================
问题:
n个大小不一的圆,放到一个矩形中,并且矩形底边相切,当然圆不能重叠
形如:OoO....oOO ,求最小长度的圆排列。
问题:
n个大小不一的圆,放到一个矩形中,并且矩形底边相切,当然圆不能重叠
形如:OoO....oOO ,求最小长度的圆排列。
算法
下列为已知条件:
r[i],i=1~n 该数组记录各圆半径 (注:初始化时,各值都是已知的)
x[i],i=1~n 该数组记录各圆圆心横坐标 (注:初始化0, 以后由程序计算才确立下来)
n 圆的个数 (注:初始化,已知的)
min 记录最小长度, (注:min初始化值蛮大的,设为100000)
下列为已知条件:
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); //计算圆排列长度
{
定义了三个私有变量,*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}
//求第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]);
}
}
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]); //恢复现场
}
}
{
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();
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之类看似很乱的东西,可以无视。
王晓冬 点到即止,指得是他对全书程序中用到变量含义作了说明。对于大多数程序,的确够用了,但一些程序,还是略有不足的。
正题...
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)+....
好了,接下来,就得到一个朴素的想法:
第t个圆的圆心横坐标=2*sqrt(r1*r2)+2*sqrt(r2*r3)+....
C.解决最后问题。里面if是怎么回事?
=》
圆1,圆2用到上面想法是正确的,问题出在后续的圆上,对于圆3,有2种可能:和圆2相切(朴素思想),另一种可能被 忽视的--和圆1 相切。
而恰恰是圆1和圆3相切这种情况,这时计算得到数据是低估的(即小于实际正确值)。
=》
圆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 前面圆非常大
现在各圆横坐标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 当然,由此可以确定的是 限制条件没办法逼出更优解。或者说,原程序提供的限制条件仅保证得到一个可行解。
残留问题: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~