Jarvis 算法
Jarvis 算法,也被称为包装算法(Gift Wrapping Algorithm),主要用于解决二维平面上的凸包问题。凸包问题是指在一个二维平面上给定一组点,找出能够包含所有这些点的最小凸多边形。下面将从算法原理、代码实现、复杂度分析等方面对 Jarvis 算法进行详细解析。
算法原理
Jarvis 算法的核心思想是从最左边的点开始,通过不断寻找相对于当前点具有最小极角的点,逐步构建凸包。具体步骤如下:
- 找到最左边的点:在所有给定的点中,找到 (x) 坐标最小的点。如果有多个点的 (x) 坐标相同,则选择 (y) 坐标最小的点。这个点一定是凸包上的一个点。
- 初始化当前点:将最左边的点作为当前点。
- 寻找下一个凸包点:从当前点出发,遍历所有其他点,计算每个点相对于当前点的极角。选择极角最小的点作为下一个凸包点。
- 更新当前点:将找到的下一个凸包点作为新的当前点。
- 重复步骤 3 和 4:直到回到最开始的点,此时凸包构建完成。
代码实现
以下是使用 Python 实现的 Jarvis 算法代码:
def orientation(p, q, r):"""计算三个点的方向:param p: 第一个点:param q: 第二个点:param r: 第三个点:return: 0 表示共线,1 表示顺时针,2 表示逆时针"""val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1])if val == 0:return 0 # 共线elif val > 0:return 1 # 顺时针else:return 2 # 逆时针def jarvis(points):"""使用 Jarvis 算法计算凸包:param points: 二维平面上的点集:return: 凸包上的点集"""n = len(points)if n < 3:return points# 找到最左边的点l = 0for i in range(1, n):if points[i][0] < points[l][0]:l = ihull = []p = lq = 0while True:hull.append(points[p])q = (p + 1) % nfor i in range(n):if orientation(points[p], points[i], points[q]) == 2:q = ip = qif p == l:breakreturn hull# 测试
points = [(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3)]
hull = jarvis(points)
print("凸包上的点:", hull)
代码解释
- orientation 函数:该函数用于计算三个点的方向。通过计算向量叉积的符号来判断三个点的相对位置,返回值为 0 表示共线,1 表示顺时针,2 表示逆时针。
- jarvis 函数:该函数实现了 Jarvis 算法的主要逻辑。首先找到最左边的点,然后通过不断寻找下一个凸包点,直到回到最开始的点。
- 主程序:定义了一组测试点,调用
jarvis
函数计算凸包,并输出凸包上的点。
复杂度分析
- 时间复杂度:O(nh)O(nh)O(nh),其中 nnn 是点的总数,hhh 是凸包上的点的数量。在最坏情况下,hhh 可能等于 nnn,此时时间复杂度为 O(n2)O(n^2)O(n2)。
- 空间复杂度:O(h)O(h)O(h),主要用于存储凸包上的点。
优缺点
- 优点:算法简单易懂,实现方便,适用于小规模数据集。
- 缺点:时间复杂度较高,在处理大规模数据集时效率较低。
其他要点
1、代码块解释
while True:
hull.append (points [p])
q = (p + 1) % n
for i in range (n):
if orientation (points [p], points [i], points [q]) == 2:
q = i
p = q
if p == l:
break
这段代码是 Jarvis 算法核心部分,其主要功能是不断寻找凸包上的点,直到形成一个完整的凸包。下面逐行对代码进行详细解释:
1.1 整体功能概述
在 Jarvis 算法中,我们从最左边的点开始,不断寻找相对于当前点具有最小极角的点作为下一个凸包上的点,重复这个过程,直到回到起始点,从而构建出整个凸包。这段代码就是实现这个过程的具体逻辑。
1.2代码逐行解释
while True:
- 这是一个无限循环,用于不断寻找凸包上的点,直到满足特定条件跳出循环。
hull.append(points[p])
hull
是一个列表,用于存储凸包上的点。points[p]
表示当前正在处理的点,将其添加到hull
列表中,意味着将该点确定为凸包上的一个点。
q = (p + 1) % n
n
是点集points
的长度。(p + 1) % n
用于计算下一个候选点的索引。使用取模运算%
是为了确保索引不会超出点集的范围,当p
达到n - 1
时,(p + 1) % n
会回到 0,形成一个循环。
for i in range(n):
- 这是一个遍历所有点的循环,目的是从所有点中找到相对于当前点
points[p]
具有最小极角的点。
if orientation(points[p], points[i], points[q]) == 2:
orientation
函数用于判断三个点的方向关系,返回值为 0 表示共线,1 表示顺时针,2 表示逆时针。- 这里的判断条件
orientation(points[p], points[i], points[q]) == 2
表示如果点points[i]
相对于点points[p]
和points[q]
是逆时针方向,说明points[i]
比points[q]
更适合作为下一个凸包上的点。
q = i
- 如果满足上述条件,将
q
的值更新为i
,即把points[i]
作为新的候选下一个凸包点。
p = q
- 循环结束后,将
p
的值更新为q
,意味着将找到的下一个凸包点作为新的当前点,继续进行下一轮的寻找。
if p == l:break
l
是最开始找到的最左边的点的索引。当p
再次等于l
时,说明已经回到了起始点,此时凸包构建完成,使用break
语句跳出无限循环。
总结
这段代码通过不断循环,从当前点出发,遍历所有点,找到相对于当前点具有最小极角的点作为下一个凸包点,直到回到起始点,从而构建出完整的凸包。
2、为什么逆时针方向说明points[i] 比 points[q] 更适合作为下一个凸包上的点?
要理解为什么当点 points[i]
相对于点 points[p]
和 points[q]
是逆时针方向时,points[i]
比 points[q]
更适合作为下一个凸包上的点,我们需要从凸包的定义和几何性质入手。
凸包的定义
凸包是包含给定点集的最小凸多边形。凸多边形的一个重要性质是,对于多边形上的任意一条边,多边形上的所有其他点都位于这条边的同一侧。也就是说,沿着凸包的边界顺时针或逆时针遍历,相邻的三条边之间的转向总是保持一致(要么都是顺时针,要么都是逆时针)。
算法寻找凸包点的原理
在 Jarvis 算法中,我们从一个已知在凸包上的点(最左边的点)开始,不断寻找下一个凸包上的点。在每一轮迭代中,我们需要找到相对于当前点 points[p]
能使凸包保持凸性的下一个点。
逆时针方向与凸包构建的关系
假设我们已经确定了当前凸包上的一个点 points[p]
,并且有一个候选的下一个点 points[q]
。现在我们遍历所有其他点 points[i]
,并通过 orientation
函数判断它们相对于 points[p]
和 points[q]
的方向。
-
逆时针方向的几何意义:当
orientation(points[p], points[i], points[q]) == 2
时,说明从points[p]
到points[q]
再到points[i]
是逆时针方向。从几何角度看,这意味着points[i]
在由points[p]
和points[q]
所确定的直线的“外侧”。 -
凸包构建的要求:为了构建一个凸包,我们希望每一步选择的点都能使凸包向外扩展,而不是向内凹陷。如果选择的点使得新的边向内凹陷,那么就不满足凸包的定义了。
-
比较
points[i]
和points[q]
:当points[i]
相对于points[p]
和points[q]
是逆时针方向时,选择points[i]
作为下一个凸包点可以保证凸包继续向外扩展,保持凸性。而如果选择points[q]
,可能会导致凸包出现向内凹陷的情况。
示例说明
假设我们有三个点 A
(对应 points[p]
)、B
(对应 points[q]
)和 C
(对应 points[i]
)。如果从 A
到 B
再到 C
是逆时针方向,那么当我们构建凸包时,选择 C
作为 A
的下一个相邻点,能够让凸包的边界向外扩展,形成一个更大的凸多边形。相反,如果选择 B
,可能会使凸包在这个位置向内弯曲,不符合凸包的定义。
综上所述,当点 points[i]
相对于点 points[p]
和 points[q]
是逆时针方向时,points[i]
比 points[q]
更适合作为下一个凸包上的点,因为这样可以保证凸包的凸性,使凸包不断向外扩展。
3、顺时针方向为什么不适合作为下一个凸包上的点?
在 Jarvis 算法构建凸包的过程中,顺时针方向的点通常不适合作为下一个凸包上的点,这与凸包的定义、几何性质以及算法的目标紧密相关,下面为你详细解释:
顺时针方向不符合凸包构建要求
- 产生凹陷情况:假设当前已经确定了凸包上的一个点 (P)(对应代码中的
points[p]
),并且有一个候选的下一个点 (Q)(对应points[q]
)。当我们遍历到另一个点 (R)(对应points[i]
),如果从 (P) 到 (Q) 再到 (R) 是顺时针方向,那么选择 (Q) 作为 (P) 的下一个相邻点,会使得凸包在这个位置向内凹陷。 - 例如,想象一个简单的场景,有三个点 (A)、(B)、(C),若按照 (A - B - C) 是顺时针顺序,当以 (A) 为起点构建凸包时,如果选择 (B) 作为下一个点,那么 (C) 就会位于 (A) 和 (B) 所构成线段的“内侧”,这样形成的多边形就不是凸多边形了,不符合凸包的定义。
- 破坏凸性:凸包要求其边界是向外扩展的,每一步选择的点都应该使凸包的范围不断扩大,且保持整体的凸性。顺时针方向的点会破坏这种凸性,导致构建出的多边形无法满足凸包的条件。
Jarvis 算法的目标和逻辑
- 目标:Jarvis 算法的目标是逐步构建一个完整的凸包,从一个已知在凸包上的点(通常是最左边的点)开始,不断寻找下一个能使凸包保持凸性的点。
- 逻辑:通过不断寻找相对于当前点具有最小极角(在逆时针方向上)的点作为下一个凸包点,算法能够保证每一步都朝着正确的方向扩展凸包。如果选择顺时针方向的点,就会与算法的这种扩展逻辑相悖,无法正确构建出凸包。
综上所述,在 Jarvis 算法中,顺时针方向的点不适合作为下一个凸包上的点,因为它会破坏凸包的凸性,不符合凸包的定义和算法的构建逻辑。