目录
知识点
1.问题
2.解析
3.设计
4.分析
5.源码
1.回溯法 摘自[中琦2513]的原创文章
(1)概念
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
(2)基本思想
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。
若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。
而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
(3)用回溯法解题的一般步骤
a.针对所给问题,确定问题的解空间: 首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解。
b.确定结点的扩展搜索规则
c.以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
圆排列问题:给定n个圆的半径序列,将它们放到矩形框中,各圆与矩形底边相切,求具有最小排列长度的圆排列。
问题描述:给定n个大小不等的圆c1,c2,…,cn,现要将这n个圆排进一个矩形框中,且要求各圆与矩形框的底边相切。圆排列问题要求从n个圆的所有排列中找出有最小长度的圆排列。例如,当n=5,且所给的5个圆的半径分别为9,3,2,7,1时,这5个圆的最小长度的圆排列为3,9,2,7,1,如图所示。
2.1小插曲
刚开始没搞懂题目啥意思,就去search了一下,然后看到了如下让我怀疑自己数学的式子
个人觉得应该把第一个式子左边的x^2改为x
2.2解析
圆排列问题的解空间是一棵排列树,利用回溯算法来遍历圆排列问题的每一种解的排列
设r=[r1,r2,……rn]是n个圆的半径,则相应的解的排列树由r[1:n]的所有排列组成
数组x存储当前圆排列的圆心横坐标
数组bestr存储最优圆排列
初始时,r是输入的n个圆的半径,计算结束后将r更新为最优解的圆排列
center计算圆在当前圆排列中的横坐标,根据勾股定理由x = sqrt((r1+r2)^2-(r1-r2)^2)推导出x = 2*sqrt(r1*r2)
compute计算当前圆排列的长度,minlen记录当前最小圆排列长度
在递归算法backtrack中,当i>n时,算法搜索至叶节点,得到新的圆排列方案。此时算法调用compute计算当前圆排列的长度,适时更新当前最优值
当i 如果不考虑计算当前圆排列中各圆的圆心横坐标和计算当前圆排列长度所需的计算时间,则 Backtrack需要O(n!)计算时间 由于算法Backtrack在最坏情况下需要计算O(n!)次圆排列长度,每次计算需要O(n)计算时间 从而整个算法的计算时间复杂性为O((n+1)!) GitHub地址3.设计
/*变量定义*/
N; //圆个数
minlen; //最小圆排列长度
x[i]; //每个圆的圆心横坐标
r[i]; //圆半径
/*自定义函数*/
//求第t个圆圆心横坐标
center(int t){
double temp = 0;
for (int j = 1; j < t; ++j) //因为目标圆有可能与排在它之前的任一圆相切,故需一一判断
{
double xvalue = x[j] + 2.0 * sqrt(r[t] * r[j]);
if (xvalue > temp)
temp = xvalue;
}
return temp;
}
//计算圆排列长度
compute(){
double low = 0, high = 0;
for (int i = 1; i < N; ++i){
low = min(low, x[i] - r[i]);
high = max(x[i] + r[i], high);
}
if (high - low < minlen){
minlen = high - low;
for (int i = 1; i < N; ++i)
bestr[i] = r[i];
}
}
//回溯算法
backtrack(int t){
if (t == N){ //计算排圆列长度
compute();
return;
}
for (int j = t; j < N; ++j){
swap(r[t], r[j]);
double centerx = center(t);
if (centerx + r[t] + r[1] < minlen){
x[t] = centerx;
backtrack(t + 1); //到第t+1个圆
}
swap(r[t], r[j]); //不能忘了恢复现场!!
}
}
4.分析
5.源码