ポイントを時計回りにソートしますか?


156

x、yポイントの配列が与えられた場合、この配列のポイントを(全体的な平均中心点の周りで)時計回りにソートするにはどうすればよいですか?私の目標は、ポイントをライン作成関数に渡し、ラインが交差することなくできるだけ凸状に見える、凸状になるようにすることです。

それだけの価値があるので、私はLuaを使用していますが、どのような疑似コードもいただければ幸いです。

更新:参考までに、これはCiamejの優れた回答に基づくLuaコードです(「app」接頭辞は無視してください)。

function appSortPointsClockwise(points)
    local centerPoint = appGetCenterPointOfPoints(points)
    app.pointsCenterPoint = centerPoint
    table.sort(points, appGetIsLess)
    return points
end

function appGetIsLess(a, b)
    local center = app.pointsCenterPoint

    if a.x >= 0 and b.x < 0 then return true
    elseif a.x == 0 and b.x == 0 then return a.y > b.y
    end

    local det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y)
    if det < 0 then return true
    elseif det > 0 then return false
    end

    local d1 = (a.x - center.x) * (a.x - center.x) + (a.y - center.y) * (a.y - center.y)
    local d2 = (b.x - center.x) * (b.x - center.x) + (b.y - center.y) * (b.y - center.y)
    return d1 > d2
end

function appGetCenterPointOfPoints(points)
    local pointsSum = {x = 0, y = 0}
    for i = 1, #points do pointsSum.x = pointsSum.x + points[i].x; pointsSum.y = pointsSum.y + points[i].y end
    return {x = pointsSum.x / #points, y = pointsSum.y / #points}
end


1
その点を通る放射状の線の角度を計算することを考えてください。次に、角度で並べ替えます。
ジェームズKポーク大統領、2011

ご存じないかもしれませんが、luaには1から#tblまでのtblのipairs(tbl)インデックスと値を反復する組み込み関数があります。だから、和演算のために、あなたは、ほとんどの人はルックスクリーナーを見つけたこれを行うことができますfor _, p in ipairs(points) do pointsSum.x = pointsSum.x + p.x; pointsSum.y = pointsSum.y + p.y end
Ponkadoodle

2
@Wallacolooそれは非常に議論の余地があります。また、バニラでは、Lua ipairsは数値のforループよりもかなり低速です。
アレクサンダーグラディッシュ

私のケースで機能させるには、いくつかの小さな変更を加える必要がありました(中心に対して2つのポイントを比較するだけです)。 gist.github.com/personalnadir/6624172 コード内の0に対するこれらのすべての比較は、任意の点ではなく、点が原点の周りに分布していると想定しているようです。また、最初の条件では、中心点より下の点が正しくソートされないと思います。コードをありがとう、それは本当に役に立ちました!
personalnadir

回答:


192

まず、中心点を計算します。次に、任意の並べ替えアルゴリズムを使用してポイントを並べ替えますが、特別な比較ルーチンを使用して、1つのポイントが他のポイントよりも小さいかどうかを判断します。

次の簡単な計算により、1つの点(a)が中心に対して他の点(b)の左側または右側にあるかどうかを確認できます。

det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y)

結果がゼロの場合、それらは中心から同じ線上にあり、正または負の場合、どちらか一方にあるため、1つの点が他の点に先行します。これを使用すると、より小の関係を構築してポイントを比較し、ソートされた配列に出現する順序を決定できます。ただし、その順序の開始位置を定義する必要があります。つまり、開始角度となる角度を指定します(たとえば、x軸の正の半分)。

比較関数のコードは次のようになります。

bool less(point a, point b)
{
    if (a.x - center.x >= 0 && b.x - center.x < 0)
        return true;
    if (a.x - center.x < 0 && b.x - center.x >= 0)
        return false;
    if (a.x - center.x == 0 && b.x - center.x == 0) {
        if (a.y - center.y >= 0 || b.y - center.y >= 0)
            return a.y > b.y;
        return b.y > a.y;
    }

    // compute the cross product of vectors (center -> a) x (center -> b)
    int det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y);
    if (det < 0)
        return true;
    if (det > 0)
        return false;

    // points a and b are on the same line from the center
    // check which point is closer to the center
    int d1 = (a.x - center.x) * (a.x - center.x) + (a.y - center.y) * (a.y - center.y);
    int d2 = (b.x - center.x) * (b.x - center.x) + (b.y - center.y) * (b.y - center.y);
    return d1 > d2;
}

これにより、12時の位置から時計回りにポイントが並べられます。同じ「時間」のポイントは、中心から遠いものから順番に並べられます。

整数型(Luaには実際には存在しない)を使用する場合は、det、d1、およびd2変数が、実行された計算の結果を保持できる型であることを確認する必要があります。

あなたはできるだけ凸などの固体見て何かを達成したい場合は、私はあなたが探していると思い凸包Graham Scanを使用して計算できます。このアルゴリズムでは、特別なピボットポイントを起点として、ポイントを時計回り(または反時計回り)にソートする必要もあります。次に、左または右に曲がって凸包に新しい点を追加するかどうかを確認するたびに単純なループ手順を繰り返します。この確認は、上記の比較関数と同様に外積に基づいています。

編集:

if (a.y - center.y >= 0 || b.y - center.y >=0)x = 0で負のyを持つポイントが中心から遠いものから順に並べ替えられるように、ifステートメントをもう1つ追加しました。同じ「時間」のポイントの順序を気にしない場合は、このifステートメントを省略して常にを返すことができますa.y > b.y

-center.xおよびを追加した最初のifステートメントを修正しました-center.y

2番目のifステートメントが追加されました(a.x - center.x < 0 && b.x - center.x >= 0)。それが欠けていることは明らかな見落としでした。一部のチェックが冗長であるため、ifステートメントをすぐに再編成できます。たとえば、最初のifステートメントの最初の条件がfalseの場合、2番目のifの最初の条件はtrueでなければなりません。ただし、簡単にするためにコードはそのままにすることにしました。コンパイラがコードを最適化し、いずれにしても同じ結果を生成する可能性は十分にあります。


25
+1:いいえatan()、平方根、分割はありません。これはコンピュータグラフィックスの考え方の良い例です。簡単なケースはすべてできるだけ早く切り捨て、ハードなケースでも、必要な答えを知るために計算をできるだけ少なくします。
RBerteig 2011

ただし、すべてのポイントを他のすべてのポイントと比較する必要があります。新しいポイントを挿入する簡単な方法はありますか?
イテレータ2011

2
ポイントのセットが事前にわかっている場合、O(n * log n)の比較のみが行われます。その間に点を追加したい場合は、それらを平衡二分探索木などのソートされたセットに保持する必要があります。このような場合、新しい点を追加するにはO(log n)の比較が必要であり、極座標を含むソリューションでもまったく同じです。
ciamej 2011

2
これはケースが欠けていますか?if(ax-center.x <0 && bx-center.x> = 0)return false;
トムマーティン

2
ちょっと、そこ。かなり古いですが、「これは12時から時計回りにポイントを並べます。」なぜ12時で、どうすれば6に変更できますか?誰か教えてもらえますか?
Ismoh

20

問題に対する興味深い代替アプローチは、巡回セールスマン問題(TSP)のおおよその最小値を見つけることです。すべてのポイントを結ぶ最短ルート。ポイントが凸型の形状を形成している場合、それは適切な解決策であるはずです。それ以外の場合は、見栄えが良いはずです(「固体」の形状は、周囲/面積の比率が低いものとして定義できます。これは、ここで最適化するものです) 。

TSPのオプティマイザの任意の実装を使用できますが、選択した言語で1トンを見つけることができると確信しています。


うわぁ。「興味深い」は控えめな表現です。:)
イテレータ2011

@イテレーター:私は自分の考えに非常に満足し、私はそれに反対票を投じることにかなりがっかりしました:-/それは有効だと思いますか?
static_rtti 2011

1
もちろん、NP完全な元のアルゴリズムではなく、多くの高速近似の1つを使用することを提案していました。
static_rtti 2011

6
追加角度ありがとうございます!非常に異なる答えでも、いくつかの有効な答えを持つことは、将来誰かがこのスレッドにつまずいて、オプションをブレインストーミングしようとしている場合に非常に役立ちます。
Philipp Lenssen、2011

1
私のアプローチはおそらく遅いですが、複雑なケースではより正確であることに注意してください。たとえば、「8」のポイントがある場合を想像してください。その場合、極座標は役に立ちません。得られる結果は、選択した中心に大きく依存します。TSPソリューションは、「ヒューリスティック」パラメーターから独立しています。
static_rtti 2011

19

あなたが求めているのは、極座標と呼ばれるシステムです。デカルト座標から極座標への変換は、どの言語でも簡単に実行できます。公式はこのセクションにあります。

極座標に変換した後、角度シータでソートするだけです。


4
これは機能しますが、順序付けの質問に答えるのに必要な以上の計算を行うという欠点もあります。実際には、実際の角度や半径方向の距離は気にせず、相対的な順序だけを考慮します。分割、平方根、およびトリガーを回避するため、ciamejのソリューションの方が優れています。
RBerteig 2011

1
あなたの基準が「より良い」であるかどうかはわかりません。たとえば、すべてのポイントを互いに比較することは、計算の無駄のようなものです。トリガーは大人を怖がらせるものではありませんか?
イテレータ2011

3
トリガーが怖いということではありません。問題は、trigは計算にコストがかかり、角度の相対的な順序を決定する必要がなかったことです。同様に、半径を整えるために平方根をとる必要はありません。デカルト座標から極座標への完全な変換は、アークタンジェントと平方根の両方を実行します。したがって、あなたの答えは正しいですが、コンピュータグラフィックスまたは計算ジオメトリのコンテキストでは、それを行うための最良の方法ではない可能性があります。
RBerteig 2011

とった。しかし、OPはcomp-geoとして投稿しませんでした。それは他の誰かによるタグでした。それでも、他の解決策はポイント数が多項式であるように見えますか、それとも間違っていますか?もしそうなら、それはトリガーよりも多くのサイクルを燃やします。
イテレータ2011

私は実際にはcomp-geoタグに気づかなかった、私は質問のための唯一の合理的なアプリケーションがどちらかでなければならない必要があると思いました。結局のところ、ポイントが数個しかない場合や、操作が十分に行われない場合は、パフォーマンスの問題が問題になります。その時点で、どうやってそれを行うかを知ることは重要になり、そのため私はあなたの答えが正しいことに同意します。だれでも説明できる「時計回りの順序」の概念を計算する方法を説明します。
RBerteig 2011

3

別のバージョン(aがbの前に反時計回りにある場合はtrueを返します):

    bool lessCcw(const Vector2D &center, const Vector2D &a, const Vector2D &b) const
    {
        // Computes the quadrant for a and b (0-3):
        //     ^
        //   1 | 0
        //  ---+-->
        //   2 | 3

        const int dax = ((a.x() - center.x()) > 0) ? 1 : 0;
        const int day = ((a.y() - center.y()) > 0) ? 1 : 0;
        const int qa = (1 - dax) + (1 - day) + ((dax & (1 - day)) << 1);

        /* The previous computes the following:

           const int qa =
           (  (a.x() > center.x())
            ? ((a.y() > center.y())
                ? 0 : 3)
            : ((a.y() > center.y())
                ? 1 : 2)); */

        const int dbx = ((b.x() - center.x()) > 0) ? 1 : 0;
        const int dby = ((b.y() - center.y()) > 0) ? 1 : 0;
        const int qb = (1 - dbx) + (1 - dby) + ((dbx & (1 - dby)) << 1);

        if (qa == qb) {
            return (b.x() - center.x()) * (a.y() - center.y()) < (b.y() - center.y()) * (a.x() - center.x());
        } else {
            return qa < qb;
       } 
    }

コンパイラー(Visual C ++ 2015でテスト済み)はdax、day、dbx、dbyを計算するジャンプを生成しないため、これはより高速です。ここにコンパイラからの出力アセンブリ:

; 28   :    const int dax = ((a.x() - center.x()) > 0) ? 1 : 0;

    vmovss  xmm2, DWORD PTR [ecx]
    vmovss  xmm0, DWORD PTR [edx]

; 29   :    const int day = ((a.y() - center.y()) > 0) ? 1 : 0;

    vmovss  xmm1, DWORD PTR [ecx+4]
    vsubss  xmm4, xmm0, xmm2
    vmovss  xmm0, DWORD PTR [edx+4]
    push    ebx
    xor ebx, ebx
    vxorps  xmm3, xmm3, xmm3
    vcomiss xmm4, xmm3
    vsubss  xmm5, xmm0, xmm1
    seta    bl
    xor ecx, ecx
    vcomiss xmm5, xmm3
    push    esi
    seta    cl

; 30   :    const int qa = (1 - dax) + (1 - day) + ((dax & (1 - day)) << 1);

    mov esi, 2
    push    edi
    mov edi, esi

; 31   : 
; 32   :    /* The previous computes the following:
; 33   : 
; 34   :    const int qa =
; 35   :        (   (a.x() > center.x())
; 36   :         ? ((a.y() > center.y()) ? 0 : 3)
; 37   :         : ((a.y() > center.y()) ? 1 : 2));
; 38   :    */
; 39   : 
; 40   :    const int dbx = ((b.x() - center.x()) > 0) ? 1 : 0;

    xor edx, edx
    lea eax, DWORD PTR [ecx+ecx]
    sub edi, eax
    lea eax, DWORD PTR [ebx+ebx]
    and edi, eax
    mov eax, DWORD PTR _b$[esp+8]
    sub edi, ecx
    sub edi, ebx
    add edi, esi
    vmovss  xmm0, DWORD PTR [eax]
    vsubss  xmm2, xmm0, xmm2

; 41   :    const int dby = ((b.y() - center.y()) > 0) ? 1 : 0;

    vmovss  xmm0, DWORD PTR [eax+4]
    vcomiss xmm2, xmm3
    vsubss  xmm0, xmm0, xmm1
    seta    dl
    xor ecx, ecx
    vcomiss xmm0, xmm3
    seta    cl

; 42   :    const int qb = (1 - dbx) + (1 - dby) + ((dbx & (1 - dby)) << 1);

    lea eax, DWORD PTR [ecx+ecx]
    sub esi, eax
    lea eax, DWORD PTR [edx+edx]
    and esi, eax
    sub esi, ecx
    sub esi, edx
    add esi, 2

; 43   : 
; 44   :    if (qa == qb) {

    cmp edi, esi
    jne SHORT $LN37@lessCcw

; 45   :        return (b.x() - center.x()) * (a.y() - center.y()) < (b.y() - center.y()) * (a.x() - center.x());

    vmulss  xmm1, xmm2, xmm5
    vmulss  xmm0, xmm0, xmm4
    xor eax, eax
    pop edi
    vcomiss xmm0, xmm1
    pop esi
    seta    al
    pop ebx

; 46   :    } else {
; 47   :        return qa < qb;
; 48   :    }
; 49   : }

    ret 0
$LN37@lessCcw:
    pop edi
    pop esi
    setl    al
    pop ebx
    ret 0
?lessCcw@@YA_NABVVector2D@@00@Z ENDP            ; lessCcw

楽しい。


1
スイッチ内の2つのreturnステートメントは数学的に同等です。切り替える理由はありますか?
unagi 2018

0
  • vector3 a =新しいvector3(1、0、0).............. wrt X_axis
  • vector3 b = any_point-中心;
- y = |a * b|   ,   x =  a . b

- Atan2(y , x)...............................gives angle between -PI  to  + PI  in radians
- (Input % 360  +  360) % 360................to convert it from  0 to 2PI in radians
- sort by adding_points to list_of_polygon_verts by angle  we got 0  to 360

最後に、Anticlockwizeでソートされた頂点を取得します

list.Reverse().................. Clockwise_order

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.