📜  门|门 CS 1997 |第 49 题(1)

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

门|门 CS 1997 |第 49 题

本题是一道经典的计算几何问题,需要找到一个多边形的所有凸包点。

题目描述

输入一个二维平面上的 $n$ 个点,输出这些点的凸包顶点。请注意,输入的点集中可能存在多个点的坐标相同。

输入格式

第一行是一个整数 $n$,表示输入的点的数量。

接下来 $n$ 行,每行包含两个整数 $x_i$ 和 $y_i$,表示第 $i$ 个点的横、纵坐标。

输出格式

按照顺时针方向依次输出凸包顶点的编号,编号从 $1$ 开始,每两个编号之间用一个空格隔开。

样例输入
5
0 0
3 1
2 2
1 1
2 1
样例输出
1 2 3 5 1
算法讲解

本题可以使用 Graham 算法求凸包,其时间复杂度为 $O(n\log n)$。算法的详细过程如下:

  1. 首先找到最下方的点,并按照极角对其他点进行排序。

  2. 从第二个点开始,依次将每个点加入凸包,同时判断当前点和其前两个点是否构成右拐,若是则将前一个点移出凸包。

  3. 最后输出凸包。

若有多个点的坐标相同,则需要特殊处理。

代码示例

以下是一个 Python 的实现示例(仅供参考):

n = int(input())
points = []
for i in range(n):
    x, y = map(int, input().split())
    points.append((x, y))

# 找到最下方的点,并按照极角排序
lowest = min(points, key=lambda p: (p[1], p[0]))
points.remove(lowest)
sorted_points = sorted(points, key=lambda p: (atan2(p[1]-lowest[1], p[0]-lowest[0]), p[0]**2 + p[1]**2))

# Graham 算法求凸包
stack = [lowest, sorted_points[0], sorted_points[1]]
for p in sorted_points[2:]:
    while len(stack) >= 2 and cross_product(stack[-2], stack[-1], p) <= 0:
        stack.pop()
    stack.append(p)

# 输出凸包顶点的编号
print(" ".join(str(points.index(p)+1) for p in stack))

其中,cross_product(p1, p2, p3) 表示点 $p1$ 到点 $p2$ 的向量与点 $p1$ 到点 $p3$ 的向量的叉积,其值为:

$$ \vec{p1p2}\times\vec{p1p3}=(x_2-x_1)(y_3-y_1)-(x_3-x_1)(y_2-y_1) $$

如果该值为正,则表示左转,需要将 $p2$ 弹出凸包;否则为右转,需要继续加入点 $p3$。

为了避免除法运算和精度误差,可以将上述算式写成如下形式:

$$ \vec{p1p2}\times\vec{p1p3}=cross(\vec{p1p2},\vec{p1p3})=\frac{\vert\vec{p1p2}\vert\cdot\vert\vec{p1p3}\vert\cdot\sin\theta}{\sin(180-\theta)}=2\cdot S_{\triangle p1p2p3} $$

其中 $S_{\triangle p1p2p3}$ 表示三角形 $p1p2p3$ 的面积,由此可以推得:

$$ \begin{aligned} cross(\vec{p1p2},\vec{p1p3})&=2\cdot S_{\triangle p1p2p3} \ &=(x_2-x_1)(y_3-y_1)-(x_3-x_1)(y_2-y_1) \end{aligned} $$

这是一个更为精确和高效的计算方法。