[算法]分治法解最近对问题

分治法解最近对问题

问题地址

洛谷

题目描述

Description

最近对问题:使用分治算法解决最近对问题。

Input

第一行为测试用例个数。后面每一行表示一个用例,一个用例为一些平面上点的集合,点与点之间用逗号隔开,一个点的两个坐标用空格隔开。坐标值都是正数。

Output

对每一个用例输出两个距离最近的点(坐标使用空格隔开),用逗号隔开,先按照第一个坐标大小排列,再按照第二个坐标大小排列。如果有多个解,则按照每个解的第一个点的坐标排序,连续输出多个解,用逗号隔开。

Sample Input 1

1
1 1,2 2,3 3,4 4,5 5,1.5 1.5

Sample Output 1

1 1,1.5 1.5,1.5 1.5,2 2
题目解析

这道题说实话挺麻烦的.

就是找到距离最近的点对集合并输出

思路解析
思路1:暴力搜索,复杂度O(N2)

两两个点遍历,遍历中更新最小距离与点集,

思路2:分治,复杂度O(NlogN2)

关于分治的一些基本思路可以看博客,该博客可以很好的提供入门思想,但该博客存在着一些问题

之所以是O(NlogN2)是因为在并归中又使用了快速排序

该算法在求出最近点对时速度很慢,也没有进行优化,也没有解释为什么要对y进行排序,其有问题的代码部分如下所示:

for (i = 0; i < k; i++)
    for (j = i + 1; j < k && S[temp[j]].y - S[temp[i]].y < d; j++) 
    {
        double d3 = dist(temp[i], temp[j]);
        if (d > d3)
            d = d3;
    }

将其优化后可以将该部分代码的复杂度变为线性,优化后是O(NlogN2),如何优化见下个思路

简而言之,该部分思路如下:

  1. 将原始数据点集按照x坐标进行排序,好根据x进行点集划分

  2. 划分,也就是并归过程

    (1)求中位线,将点集以中位线为界,分为左右两部分点集。
    (2)继续找左右两部分的中位线,继续划分。
    (3)直到中位线左右两部分的点的个数小于或等于二为止

  3. 求点集中最小距离
    (1)求中位线左边部分点集中最小距离d1,求中位线右边部分点集中最小距离d2;
    (2)dmin =min(d1,d2);
    (3)选出到中位线的距离均小于min的点,计算该部分点集中最小距离d3,dmin=min(dmin ,d3)。

思路3:分治,复杂度O(NlogN),是对思路2的优化

再理解了之前那一篇博客后,可以看这篇博客,主要理解如何使用并归特性代替快速排序以及如何加快求出最近点对,但该博客说的不是很清晰,这里总结一下

  1. 为什么要对y进行排序?

    对y排序可以快速求出最近点对,至于为什么见3

  2. 如何使用并归特性代替快速排序?

    即在原有的并归代码(merge)上,增加排序(sort_y)的代码,如果只看排序部分代码的话其实就是一个并归排序,

  3. 如何加快求出最近点对

    我们只需要搜索离当前点纵坐标距离最近的6个点即可,而无需遍历该点集中全部的点.因此要先排序选出最近的6个点,那为什么只搜索6个点就可以了呢?总结作者的话如下

    • 左点集中最近点对距离为d1,右点集中最近点对距离为d2,因此dmin =min(d1,d2);
    • 选出到中位线的距离均小于min的点,称该部分为中间点集
    • 中间点集中存在点对距离d3,并且满足d3<dmin(这个条件就是我们要寻找的) ,那么这两点的y值之差必须小于dmin (若两点之间距离小于a,两点的纵坐标差一定也小于a)
    • 因此令大矩形长为2*mindis,宽为mindis,我们只需要在这个范围内寻找小于dmin的d3
    • 因此令点A位于矩形上边界,那么所有与A有可能构成最近点对的候选点应位于矩形候选区域中,我们现在就对这个矩形区域做分析
    • 大矩形长为2 * mindis,宽为mindis,可以分成两个长为mindis,宽为mindis的小矩形,并且这两个矩形交集部分(中间那条线的点)归为左半边的矩形(这里一定要注意,重合的点归为左半边)
    • 现在我们要往矩形中,尽可能的多装点,以发现这个矩形所容纳的点是有限的
    • 每个小矩形中的点一定是满足相互之间的距离dx>=dmin的,不然在第一点中,我们的dmin就会更新为dx(这点很重要!!)
    • 要想往矩形内部多装点,且满足上述条件,那么第一个小矩形可以放4个顶点,第二个小矩形可以放在2个顶点和重合的边上,见图一中7个黄色点(如果黄色点偏左,证明在左边的小矩形内,右边的矩形只有三个点,且只要第三点在重合线上,都满足条件,为什么不在顶点上是因为会和左边的小矩形重复)
    • 若将A点(黑色点)放入,并且满足上述条件,那么可放的点最多的情况A点位置见图2所示,此时只有6个点
    • 因此得出结论,将点按照y排序后,只需要看距离最近的6个点即可,

    如果还是不理解,参考最多6个点,反证法

图片来自博客 (图片来自引用博客)

代码实现

暴力搜索1

def add_element(min_list, p1, p2):
    min_list.append(p1[0] + " " + p1[1] + ",")
    min_list.append(p2[0] + " " + p2[1] + ",")


def cal_dist(p1, p2):
    return (float(p1[0]) - float(p2[0])) ** 2 + (float(p1[1]) - float(p2[1])) ** 2

if __name__ == '__main__':
    # s = "1 1,2 2,3 3,4 4,5 5,1.5 1.5"
    l = []
    for _ in range(int(input())):
        # l.append(input().strip().split(" "))
        s = input()
        l = list(map(lambda x: str(x).split(" "), s.split(",")))
        l = sorted(l, key=lambda x: (float(x[0]), float(x[1])))  # 先排序
        n = len(l)
        min_num = -1
        min_list = []
        for i in range(n):
            for j in range(i + 1, n):
                p1, p2 = l[i], l[j]
                dis = cal_dist(p1, p2)
                if i == 0 and j == 1:
                    min_num = dis
                    add_element(min_list, p1, p2)
                    break
                if dis < min_num:
                    min_num = dis
                    min_list.clear()
                    add_element(min_list, p1, p2)
                elif dis == min_num:
                    add_element(min_list, p1, p2)
                else:
                    continue
        print("".join(min_list)[:-1])

暴力搜索2(自欺欺人分治)

def add_element(min_list, p1, p2):
    if p1[0] > p2[0]:
        p2, p1 = p1, p2
    if p1[0] == p2[0] and p1[1] > p2[1]:
        p2, p1 = p1, p2
    min_list.append(p1[0] + " " + p1[1] + ",")
    min_list.append(p2[0] + " " + p2[1] + ",")


def fun(arr1, arr2, min_num, min_list):
    # print(arr1, arr2)
    for p1 in arr1:
        for p2 in arr2:
            dis = cal_close(p1, p2)
            if dis < min_num[0]:
                min_num[0] = dis
                min_list.clear()
                add_element(min_list, p1, p2)
            elif dis == min_num[0]:
                add_element(min_list, p1, p2)
            else:
                continue
    return arr1 + arr2


def cal_close(p1, p2):
    return (float(p1[0]) - float(p2[0])) ** 2 + (float(p1[1]) - float(p2[1])) ** 2


def merge_sort(i, j, arr, min_num, min_list):
    mid = (i + j) >> 1
    if i == j:
        return [arr[i]]
    arr1 = merge_sort(i, mid, arr, min_num, min_list)
    arr2 = merge_sort(mid + 1, j, arr, min_num, min_list)
    return fun(arr1, arr2, min_num, min_list)


if __name__ == '__main__':
    # s = "1 1,2 2,3 3,4 4,5 5,1.5 1.5"
    for _ in range(int(input())):
        s = input()
        l = list(map(lambda x: str(x).split(" "), s.split(",")))
        num = [cal_close(l[0], l[1])]
        lis = []
        lis.append(l[0][0] + " " + l[0][1] + ",")
        lis.append(l[1][0] + " " + l[1][1] + ",")
        merge_sort(0, len(l) - 1, l, num, lis)
        print("".join(lis)[:-1])

分治(只计算了距离,没有收集点对),如果在此基础上添加收集点对的话,要增加不少代码…

import sys


# 计算距离
def cal_dist(p1, p2):
    return (float(p1[0]) - float(p2[0])) ** 2 + (float(p1[1]) - float(p2[1])) ** 2


# 这里就是并归排序,只是排序的依据是点y值得大小
# 这里排序要改变原始数组arr的值,后续代码中要用到arr
def sort_y(l, r, arr):
    res = []
    mid = (l + r) >> 1
    i, j = l, mid + 1
    while i <= mid and j <= r:
        if arr[i][1] < arr[j][1]:  # 比较y值大小
            res.append(arr[i])
            i += 1
        else:
            res.append(arr[j])
            j += 1
    while i <= mid:
        res.append(arr[i])
        i += 1
    while j <= r:
        res.append(arr[j])
        j += 1
    for i in range(0, r + 1 - l):
        arr[l + i] = res[i]


#
def merge(l, r, arr):
    if l == r:
        return sys.maxsize  # 如果是一个点则返回最大值
    if l + 1 == r:
        sort_y(l, r, arr)  # 两个点这里要并归一次
        return cal_dist(arr[l], arr[r])  # 返回距离
    mid = (l + r) >> 1
    d1 = merge(l, mid, arr)
    d2 = merge(mid + 1, r, arr)
    d = min(d1, d2)  # 更新左右距离中较小的
    sort_y(l, r, arr)  # 这里之后的arr[l,r]是按照y排序的了
    temp = []
    for i in range(l, r + 1):
        if abs(arr[i][0] - arr[mid][0]) <= d:
            temp.append(i)  # 寻找距离小于d的点
    #
    # temp.sort(key=lambda x: arr[x][1])  # 按照纵坐标排序  TODO:
    # 求出temp中最近的一对点
    for i in range(len(temp)): # 所以这里的temp是已经按照y排过序的了
        for j in range(i + 1, len(temp)):
            if arr[temp[i]][1] - arr[temp[j]][1] > d:  # 如果纵坐标之差大于了目标距离,那就不用算了
                continue
            if j > i + 1 + 6:  # 如果大于6个点就停止
                break
            d3 = cal_dist(arr[temp[i]], arr[temp[j]])  # 计算距离
            d = min(d, d3)  # 更新较小值
    return d


if __name__ == '__main__':
    # s = "1 1,2 2,3 3,4 4,5 5,1.5 1.5"
    l = []
    for _ in range(int(input())):
        s = input().strip().split(" ")
        l.append([float(s[0]), float(s[1])])
        # l = list(map(lambda x: [float(x.split(" ")[0]), float(x.split(" ")[1])], s.split(",")))
    # l = sorted(l, key=lambda x: (x[0], x[1]))  # 先排序
    l = sorted(l, key=lambda x: (x[0]))  # 先排序
    ans = merge(0, len(l) - 1, l)
    print(round(ans ** 0.5, 4))
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
遗传算法可以用来生成测试用例,它是一种基于生物进化理论的优化算法。下面是一个简单的步骤,说明如何使用遗传算法生成测试用例: 1. 定义问题:确定测试目标和测试需求,需要测试一个软件的特定功能或性能。 2. 确定适应度函数:将问题转化为适应度函数,该函数衡量每个个体的适应程度。在测试用例生成中,适应度函数可以根据测试目标和需求来评估测试用例的质量。 3. 初始化种群:随机生成一组初始测试用例作为种群的个体。 4. 选择操作:根据适应度函数选择部分个体作为父代,用于产生下一代个体。选择操作可以使用不同的方法,如轮盘赌选择、锦标赛选择等。 5. 交叉操作:从父代中选择两个个体,并通过交叉操作生成两个子代。交叉操作可以使用不同的方法,如单点交叉、多点交叉等。 6. 变异操作:对子代进行变异操作,以引入新的基因组合。变异操作可以随机改变个体的某些基因或参数。 7. 评估适应度:对新一代个体进行适应度评估,使用定义好的适应度函数。 8. 判断终止条件:判断是否满足终止条件,例如达到最大迭代次数或找到满足要求的测试用例。 9. 重复步骤4到步骤8,直到满足终止条件。 10. 输出结果:输出找到的最优测试用例或测试用例集。 需要注意的是,遗传算法生成的测试用例可能并不是最优,但它可以帮助发现一些较好的测试用例,以覆盖软件的不同功能和边界情况。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值