判定两个点是否在一条直线的同一侧_最近点对与凸包问题(BF)

554aa3791096a94ffe908da4d311d5c3.png

这一节内容,我们来看两个经典的几何学问题。首先是最近点对问题closest-pair problem)。问题的描述是在一个二维平面上有一些随机分布的散点,要求我们在这些散点里找到两个点,使得它们的距离最小

3d1244ae8132e57dc68c0206799f14c8.png

用暴力求解的方式解决这个问题很简单,只需要把所有的情况都列举出来,即计算出每一个点与其他所有节点的距离,然后取最小的值。如果平面上有 n 个点,那么要考虑的点对数就为

。比如下面的图中一共有 16 个点,我们就要考虑 120 种情况。

546990dd66349519b029cde0df4a0de8.png

要实现这个过程也很容易,几行代码就可以搞定。

import numpy as np

def closest_pair_bf(X, Y):
    d = np.inf  # 初始化 d 为无穷
    for i in range(len(X)-1):
        for j in range(i+1, len(X)):
            d = min((X[i] - X[j])**2 + (Y[i] - Y[j])**2)  # 计算两点之间距离
    return d  ** 1/2

其中参数XY分别表示所有散点的横纵坐标的集合。在主程序中,我们用numpy.random.normal方法生成随机数,并且用np.column_stack方法将它们打包。

np.random.seed(13)  # 为了代码的可复现性
X = np.random.normal(2, 0.5, size=[16, 1])
Y = np.random.normal(2, 0.5, size=[16, 1])
XY_pair = np.column_stack((X, Y))

最后将得到的最近点对在图中标注出来。

2c5eeb7ba8121bafea51b3e2efdd3163.png

由于有两重循环,所以我们得到算法的时间复杂度为

凸包问题

凸包问题convex-hull problem)是这里我们要讨论的第二个问题。首先要弄明白什么是凸包,在生活中,你一定见过在一个扎满钉子的木头上套上一圈橡皮筋的场景,像下面这张图那样。哈哈,实际上橡皮筋围成的多边形就是一个凸包了。

68f5dfaf577b0c5d1cc867ce773ea62f.png
图片来源:Introduction to The Design and Analysis of Algorithms, 3rd Edition

从严格意义上来讲,凸包指的是由多个点构成的凸多边形(所有内角都小于180度),并且凸包还有一点性质是平面上所有的点都位于该多边形的内部。就像上面那张图中橡皮筋把所有钉子都围住一样。所以,我们要怎么来解决这个问题呢?答案当然还是采用最“暴力”的手段,穷举所有的情况,找到我们所需要的多边形。

从构成凸包的边中我们可以观察到,除了构成边本身的两个点以外,其他所有的点都位于边的同一侧。于是我们可以根据这个性质写出代码:

def convex_hull_bf(XY_pair):
    pairs = []  # 用于存储满足条件的点
    for i in range(len(XY_pair) - 1):
        for j in range(i+1, len(XY_pair)):
            a = XY_pair[j][1] - XY_pair[i][1]
            b = XY_pair[i][0] - XY_pair[j][0]
            c = XY_pair[i][0] * XY_pair[j][1] - XY_pair[i][1] * XY_pair[j][0]
            k = 0; pos = 0; neg = 0  # pos 和 neg 用于统计直线左右两侧点的个数          
            while k < len(XY_pair):
                if k != i and k != j:
                    # 统计点位于两侧个数
                    if a * XY_pair[k][0] + b * XY_pair[k][1] - c > 0:
                        pos += 1
                    else:
                        neg += 1
                    # 如果点不在同一侧就退出循环
                    if pos != 0 and neg != 0:
                        break
                k += 1
            else:
                pairs.append((XY_pair[i], XY_pair[j]))
    return pairs

对于判断一个点是在一条直线的左侧还是右侧,我们会用到了下面的公式:

2202acbd0ad5a307a69cd3f7c2b49f38.png

其中

,
,
,构成边的两个点的坐标分别为
,
,而
分别待判定的点。
如果值大于 0,说明该点在直线的左侧;如果小于 0,说明该点在直线的右侧

最后我们将满足条件的点通过收尾相连的方式显示出来,这样我们就能画出多边形了。

619ff88c0643e1ab66aad45bc44148d6.png

作图的代码如下:

def plotting(xy_pair, polygon):
    plt.scatter(xy_pair[:, 0], xy_pair[:, 1], s=15)
    for i in range(len(polygon)):
        np_point_stack = np.stack(polygon[i], axis=1)  # 将每一个点对打包成 (xi, yi) 的形式
        plt.plot(np_point_stack[0], np_point_stack[1], c='r')
    plt.show()

最后来分析一下复杂度。我们知道,如果平面上有

个点,那么一共就有
个点对,也就是直线的条数。对于每一条直线,又要从剩下
个点中判断是否在直线的同一侧。这样一来,只要我们列出求和公式,再经过推导就能够得到复杂度为
,具体推导过程如下:

总结

我们可以看到,用暴力求解的方式解决这两个几何问题非常简单,但效率却是低,都是平方级和立方级的复杂度,所以这两种算法也只能解决小规模的问题。面对

这种大规模的问题时,我们就要求助其他算法了。

本节全部代码

← 图的两种遍历 | 算法与复杂度​zhuanlan.zhihu.com
f757a3549abf6b108196eea6ef4bdf97.png
→ 暴力搜索 | 算法与复杂度​zhuanlan.zhihu.com
f757a3549abf6b108196eea6ef4bdf97.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值