ランダムな順序で点の配列があり、点のallとその辺を通過するポリゴンを(隣接するすべてのペアが辺を表すように並べ替えることによって)見つける必要があるとします。もちろん、交差していません。
ポイントを選択し、その下にある最終的な配列にすべてのポイントを追加して、左から右に並べ替えてみました。次に、その上にあるすべてのポイントを追加し、右から左に並べ替えます。
自己交差を避けるために、ポイントを追加して自然に並べ替えることができると言われました。しかし、それを理解することはできません。これを行う簡単な方法は何ですか?
誰かが言ったように、最小の長さの解決策はまさに巡回セールスマン問題です。これは、最適ではありませんが実行可能なアプローチです。
ポイントの Delauney三角形分割 を計算します。すべてのポイントを補間する境界が残るか、それ以上セグメントを削除できなくなるまで、境界セグメントを連続して削除します。そのセグメントを使用する三角形のすべてのポイントが境界上にある場合は、境界セグメントを削除しないでください。この境界をあなたの道としてください。
私はこれをMathematicaで40個のランダムな点を使って実装しました。典型的な結果は次のとおりです。
明らかな反対意見は、すべてのポイントが境界ポイントであるとは限らないポイントに到達する可能性があることですが、境界を自己交差させずに境界セグメントを削除することはできません。これは正当な異議です。これが発生したケースを確認するのに数十回の実行が必要でしたが、最終的にこのケースが発生しました。
ローカルトポロジを使用してこれを修正するいくつかの明白な方法をおそらく見ることができますが、詳細はあなたに任せます!役立つかもしれない1つのことは、辺を共有する2つの三角形、たとえば三角形(p、q、r)と(q、p、s)を取り、それらを(r、p、s)と( r、s、q)(すべての座標は三角形の周りを反時計回りに)。これは、この変換で結果として得られる三角形も反時計回りに順序付けられている限り実行できます。
修正の必要性を減らすために、各ステップで削除するセグメントを適切に選択する必要があります。境界セグメントの長さと、候補三角形(セグメントとの潜在的な入力ポイントによって形成される三角形)の反対側の長さの合計の比率を使用しました。
私たちの戦略は、ポリゴンにすべてのポイントが含まれていることを確認し、どの線も交差しない場所でそれらを接続する順序を見つけることができる計画を立てることです。
アルゴリズム:
- 左端の点を見つけるp
- 右端の点を見つけるq
- ポイントをA(pqより下のポイントのセット)とB(pqより上のポイントのセット)に分割します[(p、q、?)の左折テストを使用して、ポイントが線より上にあるかどうかを判断できます]。
- Aをx座標で並べ替える(増加)
- Bをx座標(減少)で並べ替えます。
- Pで定義されたポリゴン、Aの点を順番に、q、Bの点を順番に返します。
ランタイム:
ステップ1、2、3にはO(n)時間がかかります。
ステップ4、5はO(nlogn)時間かかります。
ステップ6はO(n)時間かかります。
合計実行時間はO(nlogn)です。正しさ:
構造上、p、q以外のすべてのポイントは、セットAまたはセットBにあります。したがって、6行目の出力ポリゴンは、すべてのポイントを含むポリゴンを出力します。ここで、出力ポリゴンの線分が互いに交差していないことを主張する必要があります。
出力ポリゴンの各セグメントを検討してください。 pからAの最初のポイントまでの最初のエッジは、どのセグメントとも交差できません(セグメントがまだないため)。 Aのポイントをx座標で順番に進むと、各ポイントから次のセグメントが右に移動し、前のすべてのセグメントが左に移動します。したがって、pからAのすべての点を通り、点qに移動するとき、交差はありません。
QからBのポイントを経由して戻る場合も同じです。これらのセグメントは右から左に進むため、互いに交差することはできません。 Aのすべての点が線pqの下にあり、Bのすべての点がこの線より上にあるため、これらのセグメントもAの何とも交差できません。
したがって、セグメントが互いに交差することはなく、単純なポリゴンがあります。
ソース: リンク切れ
これがpython 3.6コード(必要なライブラリ:matplotlib、numpy) bdean2 's answer 。
写真の説明:
=========
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)
==========
あなたが探しているものは、文献では単純な多角化と呼ばれています。たとえば、このトピックについては このWebページ を参照してください。 Miguelが言うように、 星型 多角形を生成するのは簡単ですが、たとえば、Axel Kemperが言及するように、最小のTSPである最小の周囲の多角形を見つけるのは困難です。一般に、特定のポイントセットには指数関数的な数の異なるポリゴン化があります。
星型のポリゴン化の場合、注意が必要な問題が1つあります。余分な点x(星の「カーネル」内)はいけません。既存のポイントと一致します!これがxを保証する1つのアルゴリズムです。最も近い点のペア(a、b)を見つけ、xセグメントabの中点になります。次に、Miguelの投稿に従って続行します。
さて、実際に最小性などを気にしない場合は、凸包の内側に新しいポイントを配置してから、この新しいポイントに対して角度で他のポイントを並べ替えることができます。交差しないポリゴンを取得します。
私はこれとまったく同じ問題を抱えていて、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]))
2つのセグメントが交差するかどうかのテストは簡単で高速です。たとえば、 that を参照してください。
これで、ポリゴンを繰り返し作成できます。
ソースポイント:S = {S0, ... Si, Sj,...}
最終ポリゴン:A = {A0, ... Ai, Aj,...}
S
fullとA
emptyから始めます。
S
の最初の3ポイントを取り、それらをA
に移動します。もちろん、この三角形は自己交差していません。
次に、S
が空になるまで、最初の残りのポイントを取得します。これをP
と呼び、A
で挿入できる位置を探します。
この位置はi+1
最初のi
は、どちらも[Ai-P]
または[Ai+1-P]
他のセグメントと交差します[Ak-Ak+1]
。
したがって、新しいポリゴンA
は{A0, ... Ai, P, Ai+1, ...}
これが私のTypeScript実装です Pawel Pieczul 's answer これは単純なポリゴンを含む私のユースケースで完全に機能しました:
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
})
}