问题描述

给定n个大小不等的圆c1,c2,…,cn,现要将这n个圆排进一个矩形框中,且要求各圆与矩形框的底边相切。圆排列问题要求从n个圆的所有排列中找出有最小长度的圆排列。

问题分析

圆排列问题的解空间是一棵排列树,我们用回溯法在整个排列数中搜索最优解。
首先,我们可以把这个问题分解为以下几步:

  • 找出所有圆的排列
  • 计算出每一个排列的长度
  • 选择最小的长度

那么我们分三个步骤讨论下:

全排列

给你一组半径,每个代表一个圆,找出所有的排列方式。这其实就是全排列问题。
我们用递归的方法找出全部的排列。
比如:给你abc
其实你很容易写出他的全排列 abc acb bac bca cab cba
你可能有很多种方法来写出,这里讲解一种。
以abc为例,其长度为3,那么我们可以看做有三个空,你可以使用abc来填数。
那么面对第一个空的时候,你可以选a或者b或者c
假如你选了a,那么面对第二个空的时候,你可以选b或者c,假如你选了b,那么第三个空你只能选c
这样你可以选择出所有的排列

那么我们把这个操作用交换来实现试试
给你abc,分别在第一个位置,第二个位置和第三个位置。
那么第一个空(也就是第一个位置)应该填的有三种可能(可能a 可能b 可能c)
那我们就用第一个位置与第一、第二、第三个位置依次交换,就代表了依次选择。
那么当第一个位置的值与第二个位置交换的时候。得到了bac,此时就代表我们选定的第一个位置的字符为b,接下来的处理其实可以看作:剩下两个位置,剩下两个字符,找出他们的全排列。那岂不是可以还用刚才的方法,拿第二个位置的值与第二、第三位置分别交换,以此类推,就找到了所有的全排列。

需要注意的是,每次交换选定后,递归搜索出他后面字符的全排列后,还需要把该字符交换回去。举个例子,abc,a和b交换之后,说明第一个位置选定了a了。然后递归找以a为首的排列,全找出来之后,还得把a和b换回去,因为下面我们要把a和c交换,找以c为开头的全排列。这就是代码里面两次交换的原因。

代码:

int r[1000];//r里面存放了给定的一组数字
int N;//N代表这组数字的长度
int index=1;//index初始从1开始
void backtrack(int index){ //if(index==N+1){//已经找到了一个排列//存放在r数组中 输出r数组即可}else{for(int j=index;j<=N;j++){//index之前的已经排列好 index位置依次与后面位置的值交换 swap(r[index],r[j]);backtrack(index+1);//确定index之后,递归寻找index之后字符的全排列 swap(r[index],r[j]);//index选择j的情况已经结束 把他换回去 进行下一个交换 }}
}

计算一种排列的长度

找出了所有的排列
那么只要我们能够计算出一种排列的长度
那么所有的排列 比较一个最小的即可得到答案
怎么算呢,我们已经有了半径信息。那么假设前一个圆的圆心横坐标为x1,后一个是x2,半径是r1和r2
那么r1和r2做差就是直角边(每一个圆都与底框相切),r1+r2就是斜边,那么根据勾股定理即可计算出x
x2=sqrt((r1+r2)2−(r1−r2)2)x^2 = sqrt((r_1+r_2)^2-(r_1-r_2)^2)x2=sqrt((r1​+r2​)2−(r1​−r2​)2)推导出x = 2sqrt(r1r2)
x1+x就是x2,那么有了这个公式,我们假定第一个圆形的圆心横坐标为0,知道了第一个就能算出第二个,以此类推所有的圆心横坐标就都算出来了。

有了所有的横坐标,还有该排列所有圆的半径,那么第一个圆的横坐标加上第一个圆的半径就是该排列的最左边low
最后一个圆的横坐标加上最后一个圆的半径就是该排列的最右边high

high-low即可得到长度。

完善算法

前面的想法其实还存在一些问题。计算横坐标x的时候,我们使用的公式有一个前提,就是这个圆必须和前一个圆相切,那么实际情况中是相切的嘛

如图,是不一定的。当前圆并不一定与前一个圆相切,有可能和前一个相切,也有可能和前面任意一个圆相切。所以,我们其实需要和前面所有的圆都进行一次计算。如图,这个圆和前面相邻的那个并不相切,所以你用我们说的那个公式计算的话,会计算出来偏小的结果。所以我们只需要把当前圆与前面所有的圆依次计算,保留最大的值,就是正确的值。
还有一个问题:
我们计算左右边界的方法是对的吗?
左边界并不一定是第一个圆的左边

如图,左边界并不是第一个圆的左边,那么怎么算呢
其实我们有了每个圆的圆心横坐标和半径了,我们只要把每个圆的左边界算出来,取最小的就是整个排列的左边界了
同理,把每个圆的右边界算出来,取最大的,就是整个排列的右边界了

剪枝(回溯)

在搜索过程中,如果当前位置的排列长度已经大于维护的最小值了,那就不用继续搜索子树了
所以可以剪个枝

void backtrack(int index){if(index==N+1){//已经找到了一个排列compute();}else{for(int j=index;j<=N;j++){//index之前的已经排列好 index位置依次与后面的交换 swap(r[index],r[j]);double center_x=center(index);//计算当前第t个位置的横坐标if(center_x+r[index]+r[1]<minlen){//如果已经大于维护的最小值 则不必搜索 x[index]=center_x;//存入表示坐标的数组x中backtrack(index+1);//递归选择index+1位置 }swap(r[index],r[j]);//index选择j的情况已经结束 把他换回去 进行下一个交换 }}
}

代码实现

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
#define MAXLEN 1000
double r[MAXLEN];//存放圆排列的半径
double x[MAXLEN];//存放圆排列的圆心横坐标
double best_r[MAXLEN];//用来记录结果
int N;//输入圆的个数
double minlen=1000;void compute(){//找到了一个排列 且此半径排列存放在数组r中double low=0,high=0;for(int i=1;i<=N;i++){//算出每一个圆的左边界和右边界if(x[i]-r[i] < low) low = x[i]-r[i];if(x[i]+r[i] > high) high = x[i]+r[i]; }if(high-low<minlen){minlen=high-low;//更新最小长度for(int i=1;i<=N;i++) best_r[i]=r[i];//记录最小排列}
}double center(int t){//计算圆心坐标 double x_max=0;for(int j=1;j<t;j++){//t=1的时候 第一个圆不计算 横坐标记为0 double x_value=x[j]+2.0*sqrt(r[t]*r[j]);if(x_value>x_max) x_max=x_value;//取最大的那个计算值 } return x_max;
}void backtrack(int index){if(index==N+1){//已经找到了一个排列compute();}else{for(int j=index;j<=N;j++){//index之前的已经排列好 index位置依次与后面的交换 swap(r[index],r[j]);double center_x=center(index);//计算当前第t个位置的横坐标if(center_x+r[index]+r[1]<minlen){//如果已经大于维护的最小值 则不必搜索 x[index]=center_x;//存入表示坐标的数组x中backtrack(index+1);//递归选择index+1位置 }swap(r[index],r[j]);//index选择j的情况已经结束 把他换回去 进行下一个交换 }}
}
int main(){cin>>N;//一共N个圆 for(int i=1;i<=N;i++){cin>>r[i];//输入所有圆的半径}backtrack(1);//输出结果 cout<<minlen<<endl;for(int i=1;i<=N;i++) cout<<best_r[i]<<" ";return 0;
}

运行结果

算法优化:

复杂度:

优化:
(1)1,2,…,n-1,n和n,n-1, …,2,1这种互为镜像的排列具有相同的圆排列长度,可以计算一半,减少一半的计算量;
注意减少的是一半的计算量,全排列我们还是要搜索完的,你得找到叶子结点,才知道这个是重复的串。所以整体复杂度的数量级依然是O(n!)
(2)所给的n个圆中有k个圆有相同的半径,则这k个圆产生的k!个完全相同的圆排列,只需要计算一个。
这个可以减少搜索的排列数,但是极端情况没有改变。

圆排列问题详解(原理+代码)相关推荐

  1. 图像质量损失函数SSIM Loss的原理详解和代码具体实现

    本文转自微信公众号SIGAI 文章PDF见: http://www.tensorinfinity.com/paper_164.html http://www.360doc.com/content/19 ...

  2. TOPSIS(逼近理想解)算法原理详解与代码实现

    写在前面: 个人理解:针对存在多项指标,多个方案的方案评价分析方法,也就是根据已存在的一份数据,判断数据中各个方案的优劣.中心思想是首先确定各项指标的最优理想值(正理想值)和最劣理想值(负理想解),所 ...

  3. 数学建模_随机森林分类模型详解Python代码

    数学建模_随机森林分类模型详解Python代码 随机森林需要调整的参数有: (1) 决策树的个数 (2) 特征属性的个数 (3) 递归次数(即决策树的深度)''' from numpy import ...

  4. 逆透视变换详解 及 代码实现(二)

    根据 逆透视变换详解 及 代码实现(一)的原理 下面我用车上拍摄的车道图像,采用逆透视变换得到的图像,给出代码前我们先看下处理结果. 首先是原始图像: 下图为逆透视变换图像: 下面说具体的实现吧!! ...

  5. 逆透视变换详解 及 代码实现(一)

    逆透视变换详解 及 代码实现(一) 中主要是原理的说明: 一.世界坐标轴和摄像机坐标轴 从下图中可以看到,世界坐标为(X,Y,Z)  相机坐标为(Xc,Yc,Zc) 而世界坐标变换到相机坐标存在一个旋 ...

  6. 一文速学数模-时序预测模型(四)二次指数平滑法和三次指数平滑法详解+Python代码实现

    目录 前言 二次指数平滑法(Holt's linear trend method) 1.定义 2.公式 二次指数平滑值: 二次指数平滑数学模型: 3.案例实现 三次指数平滑法(Holt-Winters ...

  7. 逆透视变换详解 及 代码实现

    逆透视变换详解 及 代码实现(一) 中主要是原理的说明: 一.世界坐标轴和摄像机坐标轴 从下图中可以看到,世界坐标为(X,Y,Z)  相机坐标为(Xc,Yc,Zc) 而世界坐标变换到相机坐标存在一个旋 ...

  8. 技术工坊|BANCOR算法详解及代码实现(上海)

    2019独角兽企业重金招聘Python工程师标准>>> EOS项目在RAM分配中采用了Bancor算法,并将RAM的价格爆炒到了很高的价位,凭借EOS项目在区块链领域的强大运营宣传能 ...

  9. 【STM32】电容触摸按键控制模块详解(代码、流程图、每行均有注释)

    STM32-电容触摸按键控制模块详解(代码.每行均有注释) 阿汪先生用的开发板是正点原子的战舰板,但本部分代码通用于STM32的各个开发板. 本文是针对正点原子提供的例程的详细补充,重点是扫描触摸按键 ...

  10. 算法 经典的八大排序算法详解和代码实现

    算法 经典的八大排序算法详解和代码实现 排序算法的介绍 排序的分类 算法的时间复杂度 时间频度 示例 图表理解时间复杂度的特点 时间复杂度 常见的时间复杂度 空间复杂度 排序算法的时间复杂度 冒泡排序 ...

最新文章

  1. 独家 | 一文读懂PySpark数据框(附实例)
  2. 实战解析:真实AI场景下,极小目标检测与精度提升 | 百度AI公开课
  3. 升级 | Fastjson 1.2.68 发布,支持 GEOJSON
  4. mxnet dmlc-core\src\io\local_filesys.cc: Check failed: allow_null
  5. [github]使用——上传工程到新建的repo
  6. springmvc学习笔记(19)-RESTful支持
  7. jquery+ajax验证不通过也提交表单问题处理
  8. jsp中头的导入两种方式区别
  9. 四级过了,我却高兴不起来!
  10. Linux系统 nginx伪静态配置及nginx重启
  11. 敲黑板!从零开始,小白如何通过Kaggle竞赛提高数据分析能力!
  12. 如何学习帆软FineReport
  13. chrome浏览器控制台执行js脚本
  14. python 无头浏览器_python3使用无头浏览器
  15. mysql 拼音查询_MySQL拼音首字母查询
  16. 2021年人工智能五大趋势预测
  17. 主动积极:卓尔不群的个人管理策略
  18. 神经网络(4)---神经网络是如何帮助我们学习复杂的nonlinear hypotheses
  19. 最大团问题(使用递归和非递归两种方法)
  20. Linux - 操作系统

热门文章

  1. asp.net 调用echarts显示图表控件随浏览器自适应解决方案
  2. 第24周SDAI缓解能否预测远期RA骨破坏受抑制
  3. SmartFoxServer 2X 安装
  4. 删除指定路径下的文件以及文件夹
  5. 学用ASP.NET2.0
  6. 如何去除list中的重复元素
  7. mysql数据库分表及实现
  8. python类属性用法总结
  9. java设计模式-State模式
  10. Python一些常用模块