📜  一组给定点的凸包的周长(1)

📅  最后修改于: 2023-12-03 15:35:52.442000             🧑  作者: Mango

一组给定点的凸包的周长

凸包(Convex Hull)是在平面(x-y平面)中,包含一组点的最小凸多边形的边缘。给定一个点集,求出这个点集的凸包。对于凸包的周长,是指凸包上所有边的长度之和。

算法介绍

目前凸包的求解有两种经典算法:Graham算法和Jarvis算法。由于Graham算法效率高,实际应用中最广泛。

Graham算法的核心思想:以与x轴正方向夹角从小到大排序的方式,对点进行排序;扫描每个点,根据“右转”判断来决定是否放入凸包中。

时间复杂度

Graham算法的时间复杂度为O(nlogn)。

实现代码

我们可以先给出一个最简单的求凸包周长的代码:

import math

def dist(p1, p2):
    """计算两点间距离"""
    return math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)

def convex_hull(points):
    """求凸包"""
    points = sorted(set(points))  # 排序并去重
    if len(points) <= 1:
        return points
    # 从左下角和右上角开始
    lower = []
    for p in points:
        while len(lower) >= 2 and (lower[-1][0] - lower[-2][0]) * (p[1] - lower[-2][1]) - \
                (lower[-1][1] - lower[-2][1]) * (p[0] - lower[-2][0]) >= 0:
            lower.pop()
        lower.append(p)
    upper = []
    for p in reversed(points):
        while len(upper) >= 2 and (upper[-1][0] - upper[-2][0]) * (p[1] - upper[-2][1]) - \
                (upper[-1][1] - upper[-2][1]) * (p[0] - upper[-2][0]) >= 0:
            upper.pop()
        upper.append(p)
    return lower[:-1] + upper[:-1][::-1]

def perimeter(points):
    """计算周长"""
    conv = convex_hull(points)
    if len(conv) <= 1:
        return 0
    p = conv[0]
    s = 0
    for q in conv[1:]:
        s += dist(p, q)
        p = q
    return s + dist(conv[-1], conv[0])

接下来,我们对上述代码进行 markdown 格式解释:

计算两点间距离

def dist(p1, p2):
    """计算两点间距离"""
    return math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)

两点间距离可使用勾股定理计算得出,代码实现如上。

求凸包

def convex_hull(points):
    """求凸包"""
    points = sorted(set(points))  # 排序并去重
    if len(points) <= 1:
        return points
    # 从左下角和右上角开始
    lower = []
    for p in points:
        while len(lower) >= 2 and (lower[-1][0] - lower[-2][0]) * (p[1] - lower[-2][1]) - \
                (lower[-1][1] - lower[-2][1]) * (p[0] - lower[-2][0]) >= 0:
            lower.pop()
        lower.append(p)
    upper = []
    for p in reversed(points):
        while len(upper) >= 2 and (upper[-1][0] - upper[-2][0]) * (p[1] - upper[-2][1]) - \
                (upper[-1][1] - upper[-2][1]) * (p[0] - upper[-2][0]) >= 0:
            upper.pop()
        upper.append(p)
    return lower[:-1] + upper[:-1][::-1]

这部分代码使用 Graham 算法求解凸包。详细的实现过程请参考其他文献或资料。

计算周长

def perimeter(points):
    """计算周长"""
    conv = convex_hull(points)
    if len(conv) <= 1:
        return 0
    p = conv[0]
    s = 0
    for q in conv[1:]:
        s += dist(p, q)
        p = q
    return s + dist(conv[-1], conv[0])

根据求得的凸包,遍历凸包上的所有点并计算两点间距离之和,最后得出的结果即为凸包的周长。

参考资料