假设我有一个随机顺序的点数组,我需要找到一个多边形(通过排序它们,使每个相邻的对代表一个边),它通过所有点,当然,它的两边是不相交的。
我尝试通过选择一个点,并将所有点添加到其下面的最终数组,从左到右排序。然后,添加它上面的所有点,从右到左排序。
我被告知我可以添加一个额外的点并自然排序以避免自我交叉..但我无法弄明白。有什么简单的方法可以做到这一点?
答案 0 :(得分:22)
我们的策略是制定一个我们确定多边形的计划 包括所有点,我们可以找到连接它们的订单 没有线相交的地方。
<强>算法强>:
1.找到最左边的点p
2.找到最右边的点q
3.将点分为A,pq以下的点集,B,pq以上的点集[你可以使用(p,q,?)上的左转测试来 确定一个点是否在线之上] 4.按x坐标(增加)对A进行排序 5.按x坐标(减少)排序B. 6.按顺序返回由p定义的多边形,A中的点,依次为q,B点。<强>运行强>:
步骤1,2,3需要O(n)时间 步骤4,5需要O(nlogn)时间 第6步花费O(n)时间 总运行时间为O(nlogn)。<强>正确性强>:
通过构造,除了p,q之外的所有点都在集合A或中 因此,我们的第6行的输出多边形输出了一个多边形 所有要点。我们现在需要争论没有任何线段 我们的输出多边形相互交叉。考虑中的每个细分 输出多边形。从p到A中第一个点的第一个边缘不能 相交任何段(因为还没有段)。当我们继续 按顺序通过x坐标通过A中的点,从每个点开始 下一个细分市场向右走,所有之前的细分市场都是 左边。因此,当我们从p,通过A的所有点,到点 q,我们没有交叉点。
我们从q回来时也是如此 通过B点。这些段不能相互交叉 因为他们从右到左进行。这些细分也不行 在A中交叉任何东西,因为A中的所有点都在线pq之下,并且 B中的所有点都在这一行之上。
因此,没有任何片段相互交叉 其他,我们有一个简单的多边形。
来源:http://www.cs.wustl.edu/~pless/546/homeworks/hw1_selectedProblems.pdf
我知道这已经很晚了,但对未来的观众来说可能会有用。
答案 1 :(得分:8)
正如有人所说,最小长度解决方案正是旅行商问题。这是一种非最佳但可行的方法:
计算Delauney triangulation分数。连续删除边界线段,直到留下插入所有点的边界,或者不再删除任何线段。如果使用该段的三角形的所有点都在边界上,则不要删除边界线段。把这个边界作为你的道路。
我使用40个随机点在Mathematica中实现了这一点。这是典型的结果:
明显的反对意见是,你可能会到达一个点,你的所有点都不是边界点,但你不能在不使边界自相交的情况下移除边界线段。这是有效的反对意见。我花了几十次才看到发生这种情况的案例,但最后得到了这个案例:
您可能会看到一些使用本地拓扑修复此问题的明显方法,但我会将详细信息留给您!可能有帮助的一点是“边缘翻转”,其中你采用两个共享一个边的三角形,比如三角形(p,q,r)和(q,p,s),并用(r,p,s)代替它们( r,s,q)(所有坐标绕三角形逆时针方向)。只要此变换中的结果三角形也是逆时针排序,就可以完成此操作。
为了减少对修复的需求,您需要在每个步骤中对要删除的片段做出正确的选择。我使用了边界段长度与候选三角形另一边长度之和的比率(由潜在入射点与段形成的三角形)。
答案 2 :(得分:5)
这是基于bdean20&#39; s answer的python 3.6代码(所需库:matplotlib,numpy)。
图片说明:
======
import random
from operator import itemgetter
import numpy
import matplotlib
import matplotlib.pyplot
class Create_random_polygon:
def __init__(self, array, min_rand_coord = None, max_rand_coord = None, points_num = None):
self.array = array
self.min_rand_coord = min_rand_coord
self.max_rand_coord = max_rand_coord
self.points_num = points_num
def generate_random_points(self):
random_coords_list = []
for x in range(self.points_num):
coords_tuple = (random.randint(self.min_rand_coord, self.max_rand_coord),
random.randint(self.min_rand_coord, self.max_rand_coord))
random_coords_list.append(coords_tuple)
self.array = random_coords_list
return random_coords_list
def close_line_to_polygon(self):
a = self.array[0]
b = self.array[len(self.array)-1]
if a == b:
pass
else:
self.array.append(a)
def find_leftmost_point(self):
leftmost_point = None
leftmost_x = None
for point in self.array:
x = point[0]
if leftmost_x == None or x < leftmost_x:
leftmost_x = x
leftmost_point = point
return leftmost_point
def find_rightmost_point(self):
rightmost_point = None
rightmost_x = None
for point in self.array:
x = point[0]
if rightmost_x == None or x > rightmost_x:
rightmost_x = x
rightmost_point = point
return rightmost_point
def is_point_above_the_line(self, point, line_points):
"""return 1 if point is above the line
return -1 if point is below the line
return 0 if point is lays on the line"""
px, py = point
P1, P2 = line_points
P1x, P1y = P1[0], P1[1]
P2x, P2y = P2[0], P2[1]
array = numpy.array([
[P1x - px, P1y - py],
[P2x - px, P2y - py],
])
det = numpy.linalg.det(array)
sign = numpy.sign(det)
return sign
def sort_array_into_A_B_C(self, line_points):
[(x_lm, y_lm), (x_rm, y_rm)] = line_points
A_array, B_array, C_array = [], [], []
for point in self.array:
x, y = point
sing = self.is_point_above_the_line( (x, y), line_points)
if sing == 0:
C_array.append(point)
elif sing == -1:
A_array.append(point)
elif sing == 1:
B_array.append(point)
return A_array, B_array, C_array
def sort_and_merge_A_B_C_arrays(self, A_array, B_array, C_array):
A_C_array = [*A_array, *C_array]
A_C_array.sort(key=itemgetter(0))
B_array.sort(key=itemgetter(0), reverse=True)
merged_arrays = [*A_C_array, *B_array]
self.array = merged_arrays
def show_image(self, array, line_points, A_array, B_array, C_array):
[(x_lm, y_lm), (x_rm, y_rm)] = line_points
x = [x[0] for x in array]
y = [y[1] for y in array]
Ax = [x[0] for x in A_array]
Ay = [y[1] for y in A_array]
Bx = [x[0] for x in B_array]
By = [y[1] for y in B_array]
Cx = [x[0] for x in C_array]
Cy = [y[1] for y in C_array]
matplotlib.pyplot.plot(Ax, Ay, 'o', c='orange') # below the line
matplotlib.pyplot.plot(Bx, By, 'o', c='blue') # above the line
matplotlib.pyplot.plot(Cx, Cy, 'o', c='black') # on the line
matplotlib.pyplot.plot(x_lm, y_lm, 'o', c='green') # leftmost point
matplotlib.pyplot.plot(x_rm, y_rm, 'o', c='red') # rightmost point
x_plot = matplotlib.pyplot.plot([x_lm, x_rm], [y_lm, y_rm], linestyle=':', color='black', linewidth=0.5) # polygon's division line
x_plot = matplotlib.pyplot.plot(x, y, color='black', linewidth=1) # connect points by line in order of apperiance
matplotlib.pyplot.show()
def main(self, plot = False):
'First output is random polygon coordinates array (other stuff for ploting)'
print(self.array)
if self.array == None:
if not all(
[isinstance(min_rand_coord, int),
isinstance(max_rand_coord, int),
isinstance(points_num, int),]
):
print('Error! Values must be "integer" type:', 'min_rand_coord =',min_rand_coord, ', max_rand_coord =',max_rand_coord, ', points_num =',points_num)
else:
self.array = self.generate_random_points()
print(self.array)
x_lm, y_lm = self.find_leftmost_point()
x_rm, y_rm = self.find_rightmost_point()
line_points = [(x_lm, y_lm), (x_rm, y_rm)]
A_array, B_array, C_array = self.sort_array_into_A_B_C(line_points)
self.sort_and_merge_A_B_C_arrays(A_array, B_array, C_array)
self.close_line_to_polygon()
if plot:
self.show_image(self.array, line_points, A_array, B_array, C_array)
return self.array
if __name__ == "__main__":
# predefined polygon
array = [
(0, 0),
(2, 2),
(4, 4),
(5, 5),
(0, 5),
(1, 4),
(4, 2),
(3, 3),
(2, 1),
(5, 0),
]
array = None # no predefined polygon
min_rand_coord = 1
max_rand_coord = 10000
points_num = 30
crt = Create_random_polygon(array, min_rand_coord, max_rand_coord, points_num)
polygon_array = crt.main(plot = True)
==========
答案 3 :(得分:3)
您所寻求的内容在文献中称为简单多边形。例如,请参阅有关该主题的this web page。 正如米格尔所说,很容易生成star-shaped多边形,但很难 例如,正如Axel Kemper所提到的那样,找到一个最小的周边多边形,这是一个最小的TSP。对于给定的点集,通常存在指数个不同的多边形。
对于星形多边形,有一个问题需要注意:额外的点 x (在星的“内核”中)不得与现有点重合! 这是一种保证 x 的算法。找到最近的一对点( a,b ),让 x 成为段 ab 的中点。然后按照米格尔的帖子继续。
答案 4 :(得分:2)
好吧,如果你实际上并不关心最小性或类似的东西,你可以在凸包内放置新点,然后按角度对其他点进行排序。你会得到一个不相交的多边形。
答案 5 :(得分:1)
测试两个段是否相交简单快速。例如,请参阅that。
这样你可以迭代地构建你的多边形:
来源点:S = {S0, ... Si, Sj,...}
最终多边形:A = {A0, ... Ai, Aj,...}
您从S
已满,A
为空。
取S
的前3分并将其移至A
。这个三角形当然不是自相交的。
然后,在S
为空之前,取其第一个剩余点,我们将调用P
,并在A
中查找可插入的位置。
第一个i+1
的此排名为i
,以确认[Ai-P]
和[Ai+1-P]
都不会与任何其他细分[Ak-Ak+1]
相交。
您的新多边形A
因此为{A0, ... Ai, P, Ai+1, ...}
答案 6 :(得分:1)
我修改了Comrade Che中的代码 的answer以避免在退出多个最左或最右点(例如[[(10,20),(17,5),(1、16),(1、14), (20,8),(4,7),(6,9)])。主要变化是,如果存在多个最左边或最右边的点,则将其与y坐标进行比较,然后选择最底端的一个作为最左边的点 或最右边的点 这是代码:
import random
from operator import itemgetter
import numpy
import matplotlib
import matplotlib.pyplot
class Create_random_polygon:
def __init__(self, array, min_rand_coord = None, max_rand_coord = None, points_num = None):
self.array = array
self.min_rand_coord = min_rand_coord
self.max_rand_coord = max_rand_coord
self.points_num = points_num
def generate_random_points(self):
random_coords_list = []
for x in range(self.points_num):
coords_tuple = (random.randint(self.min_rand_coord, self.max_rand_coord),
random.randint(self.min_rand_coord, self.max_rand_coord))
random_coords_list.append(coords_tuple)
self.array = random_coords_list
return random_coords_list
def close_line_to_polygon(self):
a = self.array[0]
b = self.array[len(self.array)-1]
if a == b:
pass
else:
self.array.append(a)
def find_leftmost_point(self):
leftmost_point = None
leftmost_x = None
leftmost_y = None
for point in self.array:
x = point[0]
y = point[1]
if (leftmost_x == None) or (x < leftmost_x) or (x == leftmost_x and y < leftmost_y):
leftmost_x = x
leftmost_y = y
leftmost_point = point
return leftmost_point
def find_rightmost_point(self):
rightmost_point = None
rightmost_x = None
rightmost_y = None
for point in self.array:
x = point[0]
y = point[1]
if (rightmost_x == None) or (x > rightmost_x) or (x == rightmost_x and y < rightmost_y ):
rightmost_x = x
rightmost_y = y
rightmost_point = point
return rightmost_point
def is_point_above_the_line(self, point, line_points):
"""return 1 if point is above the line
return -1 if point is below the line
return 0 if point is lays on the line"""
px, py = point
P1, P2 = line_points
P1x, P1y = P1[0], P1[1]
P2x, P2y = P2[0], P2[1]
array = numpy.array([
[P1x - px, P1y - py],
[P2x - px, P2y - py],
])
det = numpy.linalg.det(array)
sign = numpy.sign(det)
return sign
def sort_array_into_A_B_C(self, line_points):
[(x_lm, y_lm), (x_rm, y_rm)] = line_points
A_array, B_array, C_array = [], [], []
for point in self.array:
x, y = point
sing = self.is_point_above_the_line( (x, y), line_points)
if sing == 0:
C_array.append(point)
elif sing == -1:
A_array.append(point)
elif sing == 1:
B_array.append(point)
return A_array, B_array, C_array
def sort_and_merge_A_B_C_arrays(self, A_array, B_array, C_array):
A_C_array = [*A_array, *C_array]
A_C_array.sort(key=itemgetter(0))
B_array.sort(key=itemgetter(0), reverse=True)
merged_arrays = [*A_C_array, *B_array]
self.array = merged_arrays
def show_image(self, array, line_points, A_array, B_array, C_array):
[(x_lm, y_lm), (x_rm, y_rm)] = line_points
x = [x[0] for x in array]
y = [y[1] for y in array]
Ax = [x[0] for x in A_array]
Ay = [y[1] for y in A_array]
Bx = [x[0] for x in B_array]
By = [y[1] for y in B_array]
Cx = [x[0] for x in C_array]
Cy = [y[1] for y in C_array]
matplotlib.pyplot.plot(Ax, Ay, 'o', c='orange') # below the line
matplotlib.pyplot.plot(Bx, By, 'o', c='blue') # above the line
matplotlib.pyplot.plot(Cx, Cy, 'o', c='black') # on the line
matplotlib.pyplot.plot(x_lm, y_lm, 'o', c='green') # leftmost point
matplotlib.pyplot.plot(x_rm, y_rm, 'o', c='red') # rightmost point
x_plot = matplotlib.pyplot.plot([x_lm, x_rm], [y_lm, y_rm], linestyle=':', color='black', linewidth=0.5) # polygon's division line
x_plot = matplotlib.pyplot.plot(x, y, color='black', linewidth=1) # connect points by line in order of apperiance
matplotlib.pyplot.show()
def main(self, plot = False):
'First output is random polygon coordinates array (other stuff for ploting)'
print(self.array)
if self.array == None:
if not all(
[isinstance(min_rand_coord, int),
isinstance(max_rand_coord, int),
isinstance(points_num, int),]
):
print('Error! Values must be "integer" type:', 'min_rand_coord =',min_rand_coord, ', max_rand_coord =',max_rand_coord, ', points_num =',points_num)
else:
self.array = self.generate_random_points()
print(self.array)
x_lm, y_lm = self.find_leftmost_point()
x_rm, y_rm = self.find_rightmost_point()
line_points = [(x_lm, y_lm), (x_rm, y_rm)]
A_array, B_array, C_array = self.sort_array_into_A_B_C(line_points)
self.sort_and_merge_A_B_C_arrays(A_array, B_array, C_array)
self.close_line_to_polygon()
if plot:
self.show_image(self.array, line_points, A_array, B_array, C_array)
return self.array
if __name__ == "__main__":
# predefined polygon
array = [
(0, 0),
(2, 2),
(4, 4),
(5, 5),
(0, 5),
(1, 4),
(4, 2),
(3, 3),
(2, 1),
(5, 0),
]
#array = [(10, 20), (17, 5), (1, 16), (1, 14), (20, 8), (4, 7), (6, 9)]
#array = [(1, 19), (12, 18), (10, 1), (1, 9), (5, 16), (10, 18), (2, 1)]
#array = [(13, 17), (15, 3), (14, 13), (11, 8), (7, 16), (7, 7), (10, 15)]
array = None # no predefined polygon
min_rand_coord = 1
max_rand_coord = 10000
points_num = 30
crt = Create_random_polygon(array, min_rand_coord, max_rand_coord, points_num)
polygon_array = crt.main(plot = True)
答案 7 :(得分:0)
我相信您可以使用Graham scan算法来解决您的问题。
编辑:一般来说,Convex hull algorithms是值得关注的。
答案 8 :(得分:0)
我只是遇到了同样的问题,并提出了一些非常简单的解决方案,也具有n * log(n)的复杂性。
首先获取图形内部的某个点,这无关紧要,无论是最远点的中间还是所有点的平均值(例如重心),将其作为中心点都有意义)。
然后根据从中心点可以看到它们的角度对所有点进行排序。排序键相当于一个点和中心的atan2。
就是这样。假设p是点(x,y)的数组,这就是Python代码。
center = reduce(lambda a, b: (a[0] + b[0], a[1] + b[1]), p, (0, 0))
center = (center[0] / len(p), (center[1] / len(p)))
p.sort(key = lambda a: math.atan2(a[1] - center[1], a[0] - center[0]))
答案 9 :(得分:0)
这是我的Pawel Pieczul的answer的Typescript实现,非常适合我的涉及简单多边形的用例:
interface Point {
x: number,
y: number,
z?: number,
}
const getCentroid = (points: Point[]) => {
let centroid = { x: 0, y: 0 }
for (let i = 0; i < points.length; i++) {
centroid.x += points[i].x
centroid.y += points[i].y
}
centroid.x /= points.length
centroid.y /= points.length
return centroid
}
export const sortNonIntersecting = (points: Point[]) => {
let center = getCentroid(points)
return points.slice().sort((a: Point, b: Point) => {
let angleA = Math.atan2(a.y - center.y, a.x - center.x)
let angleB = Math.atan2(b.y - center.y, b.x - center.x)
return angleA - angleB
})
}
答案 10 :(得分:0)
Flutter 和 Dart 开发人员可以使用它。我使用它来修复用户选择的点以创建多边形。用户在地图上绘制多边形时,一般不会按顺序标记点。
这是 Pawel 答案的 dart 实现;
LatLng findCentroid(List<LatLng> points) {
double x = 0;
double y = 0;
for (LatLng p in points) {
x += p.latitude;
y += p.longitude;
}
LatLng center = new LatLng(0, 0);
center.latitude = x / points.length;
center.longitude = y / points.length;
return center;
}
List<LatLng> sortVerticies(List<LatLng> points) {
// get centroid
LatLng center = findCentroid(points);
points.sort((a, b){
double a1 = (radsToDegrees(Math.atan2(a.latitude - center.latitude, a.longitude - center.longitude)) + 360) % 360;
double a2 = (radsToDegrees(Math.atan2(b.latitude - center.latitude, b.longitude - center.longitude)) + 360) % 360;
return (a1 - a2).toInt();
});
return points;
}
num radsToDegrees(num rad) {
return (rad * 180.0) / Math.pi;
}