圆排列问题的回溯法解决及演示

问题描述:

给出一组圆,将他们排列在一条数轴上,每个圆均与数轴相切,同时每个圆之间不能重叠。将圆先后的排列在数轴上,找到在数轴上留下的最短距离。

简单图示:

需要通过圆的不同排列使得x的值最小。

问题关键点分析:

1、不论是首圆还是后面的圆,每个圆的圆心的横坐标都必须是大于等于它本身的半径。因为如果后圆半径远大于首圆,可能会出现后圆超出起始点的情况。

2、前圆不一定与后圆相切,情况可以参考下图所示;

后圆需要考虑与前面每个圆相切时的位置。

3、当两圆相切时,有如下计算公式:

d_{k} = \sqrt{\left ( r_{k} + r_{k-1}\right )^{2} - \left ( r_{k} - r_{k-1}\right )^{2}} = 2\sqrt{r_{k-1}r_{k}}

d_{k}为前圆与相切的后圆之间圆心的横坐标的距离;

r_{k}为相切后圆的半径;

r_{k-1}为前圆的半径;

4、当现有圆排列组合的长度大于已知最小排列长度后,不再继续向后调换排列,回溯向前。

5、使用排列树实现算法回溯与迭代。

以5个圆,半径为1、2、3、4、5为例:

6、使用EasyX来绘制算法过程演示图像。

代码展示:

#include <iostream> 
#include <bits/stdc++.h>
#include <graphics.h> // Include the EasyX header file
#include <conio.h>
#include <windows.h>

using namespace std; // Add this line to use std namespace
void circle(int x, int y, int radius);//画无填充的圆。

int n; // 圆的个数
float a[100]; //圆的半径
float a_x[100];//初始圆的横坐标
float x[100];//计算当前圆排列坐标 
float minn = 100000;
void Compute() {

	float w = 0;
	int i = 0;
	float temp = 0;
	for (i = 0; i < n; i++)
	{
		temp = x[i] + a[i];
		if (w < temp)
		{
			w = temp;
		}
	}
	//w = x[n-1]+a[n-1];
	if (w < minn)
	{
		minn = w;
	}

	cout << "min=" << w << endl;
}
void Print(float b[100]) {
	Sleep(500);
	cleardevice();
	setlinestyle(PS_SOLID, 3);
	setlinecolor(RED);//线的颜色为红色
	float temp = 0;
	for (int i = 0; i < n; i++)
	{

		cout << b[i] << " ";

		//temp = (x[i] * 20) + b[0] * 20;
		temp = x[i] * 20;
		circle((int)b[i] * 20, (int)temp, (int)b[i] * 20);

	}
	//Sleep(500);

	cout << endl;
}

float Center(int t) {//求圆心坐标:假定它跟前面的所有圆相切,求出圆心坐标,值最大的就是符合条件的。
	float ans = a[t];

	for (int i = 0; i < t; i++) {
		float v = x[i] + 2.0*sqrt(a[t] * a[i]);//2.0*sqrt(a*b)=sqrt(pow(a+b,2),pow(a-b,2))
		//float v = x[i - 1] + 2.0*sqrt(a[t] * a[i]);//2.0*sqrt(a*b)=sqrt(pow(a+b,2),pow(a-b,2))
		if (v > ans) ans = v;
		
	}
	return ans;
}

void Swap(float &a, float &b) {
	float t = a; a = b; b = t;
}

//排列树
void BackTrack(int t) { //第t个顶点 
	
	if (t >= n) {//到达叶结点
		Compute();
		Print(a);
		return;
	}
	else {
		for (int i = t; i < n; i++) {//不断交换剩下的圆的位置。
			Swap(a[t], a[i]);
			float centerx = Center(t);//求t的圆心坐标	
			if (t == 0)
			{
				centerx = a[0];
			}
			if (centerx + a[t] < minn) {
				x[t] = centerx;
				BackTrack(t + 1);
				
			}
			Swap(a[t], a[i]);
		}
	}
}

int main()
{
	float temp = 0;
	memset(a, 0, sizeof(a));
	memset(a_x, 0, sizeof(a_x));
	memset(x, 0, sizeof(x));
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	initgraph(960, 960);
	setlinestyle(PS_SOLID, 3);
	setlinecolor(RED);//线的颜色为红色

	for (int i = 0; i < n; i++)
	{

		a_x[i] = a[i] + temp;
		temp = temp + 2 * a[i];
		circle((int)(a[i] * 20), (int)(a_x[i] * 20), (int)(a[i] * 20));
		x[i] = a_x[i];
	}

	BackTrack(0);


	cout << "minn=" << minn << endl;

	_getch();
	closegraph();

	return 0;
}

生成好的release版exe与程序已打包上传。

推荐演示使用参数:

圆的个数<=7,圆的半径总和<=24。

回溯算法虽然不能将问题的时间复杂度下降到多项式级别,但是对于很多具体的问题还是能够有效的降低消耗时间。例如如果本问题中对7个圆进行排列,如果使用蛮力算法,需要穷举5040种组合可能,如果每种都花费0.5秒进行显示,那么总共需要花费42分钟左右。但是使用回溯法,大约只需要探索数百条左右的组合可能就完成了。如果是把每个长度更新的时候再进行展示,那么大约仅需数十次展示就完成了。这大大节省了相应的时间。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不冰微糖K

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值