回溯法求解圆排序问题

1.问题描述

问题描述

给定n个大小不等的圆 C1,C2,C3,...Cn, 现要将这n个圆排进一个矩形框中,且要求各圆与矩形框的底边相切。圆排列问题要求从n个圆的所有排序中找出最小长度的圆排序。`

问题分析------举例
例如,当n=3,且所给的3个圆的半径分别为1,1,2时,这三个圆有两种可能:

CASE1.png

CASE2.png

显然第二种情况的长度要小于第一种情况下的长度


2. 算法分析

回溯法最重要的就是求出界限函数,按问题性质,画出子集树或者排列树
在圆排列问题中,它的解空间是一棵排列树。设开始时数组a是所给n个圆的半径,则相应排列树由 a[1:n] 的所有排列构成,如下:


permutation tree.png

使用回溯遍历所有情况,在满足下界情况(即当不满足下界条件时,要剪枝),且遍历到叶子结点时,计算当前情况的圆排列长度,并更新当前最优值。


3.算法模型

数组r[ ] -起始表示是输入的n个圆的半径 ;计算结束后返回的数组a是相应于最优解的圆排列。
数组x[ ] -则记录当前圆排列中各圆的圆心横坐标。

backtrack(t) -回溯算法,t 表示选择第t个圆,当 t=N 时返回找到的最小的圆排列长度。
center(t) -计算圆在当前圆排列中的横坐标
compout() -计算当前圆排列的长度。
变量minlen -记录当前最小圆排列长度。

一一解释三个函数 :

center(t) 函数

example1.png

--计算第 t 个圆圆心横坐标。由 例1.png易知d[k]^2= sqrt((r[k]+r[k-1])2-(r[k]-r[k-1])2),推导出d[k] = 2sqrt(r[k] r[k-1]) 从而得x = 2*sqrt(r1*r2)。如图例2.png所示,在计算第三个圆 R[3] 的圆心横坐标时,不能确定是 R[1] 与它相切,还是 R[2] 与它相切,故求解第 t 个圆的圆心坐标的思想是遍历在t之前的所有圆,并计算当每一个圆与 R[t] 相切时的圆心坐标,最大值即为 R[t] 实际的圆心坐标
(伪码)

def center(t):
    temp = 0
    for i in range(1, t):
        xvalue = x[i] + 2.0 * math.sqrt(r[t] * r[i])   #temp暂时存储最大值即第 t 个圆可能圆心坐标
        if xvalue > temp:
            temp = xvalue
    return temp


compute() 函数

example2.png

--计算当前圆排列长度。用变量lenmin记录当前最小圆排列长度;数组 r[] 存储所有圆的半径。数组 x[] 则记录当前圆排列中各圆的圆心横坐标。用 low 和 high 指针分别指向圆排列的最左端和最右端,如例1.png 所示,bestr[] 数组记录当前排列方式(即排列树的一条路径)。

def compute():
low, high = 0, 0
    for i in range(1, N):
        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    #用变量lenmin记录当前最小圆排列长度
        for i in range(1, N):   #bestr[] 数组记录当前排列方式(即排列树的一条路径)
            bestr[i] = r[i]


backtrack(t) 函数

排列树.png

--当t=n时,表示n个圆选择完毕,利用compute()函数得到最小圆排列长度,并保存此时的圆序列;
当t<n时,在r[t+1],r[t+2] .. r[n] 中选择第t个圆r[j],swap r[j],r[t],则此时的r[0]到r[t]为第t个圆为r[j]的圆排序;
下一步,对其进行是否符合剪枝条件的判断,若符合则舍去,并回溯到上一层,即恢复现场,swap r[j],r[t] ;若不符合,则,计算第t个圆的圆心坐标,递归选择下一个圆。
剪枝条件:r[0]到r[t]的圆心距加r[0],r[t]的半径要小于当前最小值

如排列树.png为例,当在第一棵树在第一层搜索第二层结点时即backtrack(2)时,可选择的点为a2,a3;选择a2为第二层结点时,判断a1a2的圆心距+a1的半径+a2的半径是否小于minlen,若小于则递归选择第三层;若不符合退回第一层,选择a3,以此类推。

def backtrack(t):
    if t == N:
        compute()
    else:
        # r[t]-r[N]为未被选择的圆
        for j in range(t, N):
            # 将第j个圆选入排序中
            r[t], r[j] = r[j], r[t]
            if  center(t) + r[t] + r[1] < minlen:
                x[t] = centerx
                backtrack(t + 1)
            # 恢复现场
            r[t], r[j] = r[j], r[t]

4.代码实现

# 回溯法

import math
N = 4
# N = int(input('n='))
minlen = 999
bestr = [0 for i in range(0, N)]
r = [0 for i in range(0, N)]
x = [0 for i in range(0, N)]

r[1], r[2], r[3] =1,1,2
# for i in range(1, N):
#     r[i] = int(input('r='))


# 计算当前所选择圆的圆心横坐标
def center(t):
    temp = 0
    for i in range(1, t):
        xvalue = x[i] + 2.0 * math.sqrt(r[t] * r[i])
        if xvalue > temp:
            temp = xvalue
    return temp




# 计算当前圆排列的长度
# 变量lenmin记录当前最小圆排列长度。
# 数组r存储所有圆的半径。数组x则记录当前圆排列中各圆的圆心横坐标
def compute():
    global minlen
    low, high = 0, 0
    for i in range(1, N):
        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 i in range(1, N):
            bestr[i] = r[i]


# 回溯 选择第t个圆
def backtrack(t):
    if t == N:
        compute()
    else:
        # r[t]-r[N]为未被选择的圆
        for j in range(t, N):
            # 将第j个圆选入排序中
            r[t], r[j] = r[j], r[t]
            centerx = center(t)
            # 剪枝-当末端3个半径逐一递增或递减时不可能为最优解,舍去
        
            if centerx + r[t] + r[1] < minlen:
                x[t] = centerx
                backtrack(t + 1)
            # 恢复现场
            r[t], r[j] = r[j], r[t]

backtrack(1)
print('最小圆排列长度为 ' + repr(minlen))
print('最优圆排列的顺序对应半径分别为',end=';')
for i in range(1, N):
    print(repr(bestr[i]), end=' ')

4.优化与改进

  1. 例如,像1,2,…,n-1,n和n,n-1, …,2,1这种互为镜像的排列显然具有相同的圆排列长度,通过算法优化,可减少约一半的计算量。
  2. 如果所给的n个圆中有k个圆有相同的半径,则这k个圆产生的 k! 个完全相同的圆排列也显然相同。
  3. 剪枝优化:当后三个圆的半径为递增或者递减时,肯定不是最小长度,剪枝。

代码实现

第一部分

# z-奇数为0 偶数为1
z= (N-1)% 2
# 剪掉一半的子树-偶数则搜索2/N个子树,奇数则搜索2/N+1个子树
for i in range(1,int((N-1)/2)+z+1):
    x[1]=0
    r[1],r[i]=r[i],r[1]
    backtrack(2)

第三部分

def backtrack(t):
    if t == N:
        compute()
    else:
        # r[t]-r[N]为未被选择的圆
        for j in range(t, N):
            # 将第j个圆选入排序中
            r[t], r[j] = r[j], r[t]
            # 剪枝优化
            if t>4:
                if (r[t]>r[t-1] and r[t-1]>r[t-2] )or(r[t]<r[t-1]and r[t-1]<r[t-2]):
                    return
            if center(t) + r[t] + r[1] < minlen:
                x[t] = center(t)
                backtrack(t + 1)
            # 恢复现场
            r[t], r[j] = r[j], r[t]

代码优化部分 待续~

5.时间复杂度和空间复杂度

时间复杂度:由回溯法的排列树可知,搜索子结点的时间复杂度是O(n!)次,即全排列的时间复杂度,而Backtrack()函数每次计算需要O(n)计算时间,从而整个算法的计算时间复杂性为O((n+1)!)。
虽然理论上时间复杂度很大,但实际的消耗实际由于增加了剪枝条件,会比O((n+1)!)小很多。
空间复杂度:由于r[]数组,x[]数组的大小都为n,故空间复杂度为O(n)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容