web-dev-qa-db-ja.com

4つのポイントを時計回りに並べ替える

配列内の4つの2Dポイント。時計回りに並べ替える必要があります。たった1回のスワップ操作でできると思いますが、正式に書き留めることはできませんでした。

編集:私の場合、4つのポイントは凸多角形です。

編集:4つのポイントは凸多角形の頂点です。それらは整然としている必要はありません。

29
Agnel Kurian

より数学的な視点を取りたい場合は、4点の順列を考慮することができます

私たちの場合、時計回りに4つの順列があります

A B C D
B C D A
C D A B
D A B C

他のすべての可能な順列は、0または1のスワップでこれらの形式の1つに変換できます。 (対称であるため、Aで始まる順列のみを考慮します)

  1. A B CD-完了
  2. A B DC-CとDを交換する
  3. A C BD-スワップBとC
  4. A C DB-スワップAとB
  5. A D BC-スワップAとD
  6. A D CB-スワップBとD

したがって、必要なスワップは1つだけですが、どれを識別するには多少の作業が必要になる場合があります。

最初の3つのポイントを見て、ABCの署名された領域の符号を確認することにより、それらが時計回りであるかどうかを判断できます。それらが時計回りの場合、ケース12または5になります

これらのケースを区別するには、さらに2つの三角形をチェックする必要があります。ACDが時計回りの場合は、ケース1に絞り込むことができます。それ以外の場合は、ケース2または5にする必要があります。

ケース2と5のどちらかを選択するために、ABDをテストできます

ABCの場合も同様に反時計回りに確認できます。

最悪の場合、3つの三角形をテストする必要があります。

ポイントが凸状でない場合は、内側のポイントを見つけ、残りを並べ替えてから、任意のエッジに追加します。クワッドが凸状である場合、4つのポイントがクワッドを一意に決定しなくなり、3つの等しく有効なクワッドがあることに注意してください。

18
Oliver Hallam

ここで検討する価値のあるいくつかの考え。

  • 時計回りは、原点に関してのみ意味があります。私は、原点を一連の点の重心と考える傾向があります。例えばおそらく非常に離れた原点ではなく、4つのポイントの平均位置にあるポイントに対して時計回りに。

  • A、b、c、dの4つのポイントがある場合、オリジンの周りにそれらのポイントの時計回りの順序が複数存在します。たとえば、(a、b、c、d)が時計回りの順序を形成した場合、(b、c、d、a)、(c、d、a、b)、および(d、a、b、c)も同様になります。

  • あなたの4つのポイントはすでにポリゴンを形成していますか?もしそうなら、それはポイントをソートするのではなく、巻線をチェックして逆にすることの問題です。 a、b、c、dはd、c、b、aになります。そうでない場合は、ウェッジの応答に従って、各ポイントと原点の間の結合方位に基づいて並べ替えます。

編集:交換するポイントに関するコメントについて;

三角形(a、b、c)の場合、3番目の点cが線の右側にある場合は時計回りであると言えますab =。次のサイド関数を使用して、ポイントの座標に基づいてこれを決定します。

int side(double x1,double y1,double x2,double y2,double px,double py)
{
 double dx1,dx2,dy1,dy2;
 double o;

 dx1 = x2 - x1;
 dy1 = y2 - y1;
 dx2 = px - x1;
 dy2 = py - y1;
 o = (dx1*dy2)-(dy1*dx2);
 if (o > 0.0) return(LEFT_SIDE);
 if (o < 0.0) return(RIGHT_SIDE);
 return(COLINEAR);
}

4点の凸多角形(a、b、c、d)がある場合、これを2つの三角形(a、b、c)と(c、d、a)と見なすことができます。 (a、b、c)が反時計回りの場合、巻線(a、b、c、d)を(a、d、c、b)に変更して、ポリゴン全体の巻線を時計回りに変更します。

私が話していることを確認するために、いくつかのサンプルポイントを使用してこれを描画することを強くお勧めします。凹多角形、同一線上の点、一致点など、対処すべき例外的なケースがたくさんあることに注意してください...

6
SmacL

誰かが興味を持っているなら、これが同様の問題に対する私の迅速で汚い解決策です。

私の問題は、長方形の角を次の順序で並べることでした。

左上>右上>右下>左下

基本的には左上隅から時計回りに並べます。

アルゴリズムのアイデアは次のとおりです。

コーナーを行で並べ替えてから、コーナーペアを列で並べ替えます。

// top-left = 0; top-right = 1; 
// right-bottom = 2; left-bottom = 3;
List<Point> orderRectCorners(List<Point> corners) {    
    if(corners.size() == 4) {    
        ordCorners = orderPointsByRows(corners);

        if(ordCorners.get(0).x > ordCorners.get(1).x) { // swap points
            Point tmp = ordCorners.get(0);
            ordCorners.set(0, ordCorners.get(1));
            ordCorners.set(1, tmp);
        }

        if(ordCorners.get(2).x < ordCorners.get(3).x) { // swap points
            Point tmp = ordCorners.get(2);
            ordCorners.set(2, ordCorners.get(3));
            ordCorners.set(3, tmp);
        }               
        return ordCorners;
    }    
    return empty list or something;
}

List<Point> orderPointsByRows(List<Point> points) {
    Collections.sort(points, new Comparator<Point>() {
        public int compare(Point p1, Point p2) {
        if (p1.y < p2.y) return -1;
        if (p1.y > p2.y) return 1;
        return 0;
        }
    });
    return points;
}
3
Rui Marques

オリバーは正しい。このコード(コミュニティはwikified)は、4点の配列のすべての可能な組み合わせを生成してソートします。

#include <cstdio>
#include <algorithm>

struct PointF {
    float x;
    float y;
};

// Returns the z-component of the cross product of a and b
inline double CrossProductZ(const PointF &a, const PointF &b) {
    return a.x * b.y - a.y * b.x;
}

// Orientation is positive if abc is counterclockwise, negative if clockwise.
// (It is actually twice the area of triangle abc, calculated using the
// Shoelace formula: http://en.wikipedia.org/wiki/Shoelace_formula .)
inline double Orientation(const PointF &a, const PointF &b, const PointF &c) {
    return CrossProductZ(a, b) + CrossProductZ(b, c) + CrossProductZ(c, a);
}

void Sort4PointsClockwise(PointF points[4]){
    PointF& a = points[0];
    PointF& b = points[1];
    PointF& c = points[2];
    PointF& d = points[3];

    if (Orientation(a, b, c) < 0.0) {
        // Triangle abc is already clockwise.  Where does d fit?
        if (Orientation(a, c, d) < 0.0) {
            return;           // Cool!
        } else if (Orientation(a, b, d) < 0.0) {
            std::swap(d, c);
        } else {
            std::swap(a, d);
        }
    } else if (Orientation(a, c, d) < 0.0) {
        // Triangle abc is counterclockwise, i.e. acb is clockwise.
        // Also, acd is clockwise.
        if (Orientation(a, b, d) < 0.0) {
            std::swap(b, c);
        } else {
            std::swap(a, b);
        }
    } else {
        // Triangle abc is counterclockwise, and acd is counterclockwise.
        // Therefore, abcd is counterclockwise.
        std::swap(a, c);
    }
}

void PrintPoints(const char *caption, const PointF points[4]){
    printf("%s: (%f,%f),(%f,%f),(%f,%f),(%f,%f)\n", caption,
        points[0].x, points[0].y, points[1].x, points[1].y,
        points[2].x, points[2].y, points[3].x, points[3].y);
}

int main(){
    PointF points[] = {
        {5.0f, 20.0f},
        {5.0f, 5.0f},
        {20.0f, 20.0f},
        {20.0f, 5.0f}
    };

    for(int i = 0; i < 4; i++){
        for(int j = 0; j < 4; j++){
            if(j == i)  continue;
            for(int k = 0; k < 4; k++){
                if(j == k || i == k) continue;
                for(int l = 0; l < 4; l++){
                    if(j == l || i == l || k == l) continue;
                    PointF sample[4];
                    sample[0] = points[i];
                    sample[1] = points[j];
                    sample[2] = points[k];
                    sample[3] = points[l];

                    PrintPoints("input: ", sample);
                    Sort4PointsClockwise(sample);
                    PrintPoints("output: ", sample);
                    printf("\n");
                }
            }
        }
    }

    return 0;
}
3
Agnel Kurian

すべてのポイント順列について、靴紐の式(面積が正または負になるような絶対値がない)を使用して座標から面積を計算します。最大面積の値は、直接の単純な四辺形に対応しているようです: 靴紐の式で見つかった単純な直接の四辺形

enter image description here

2
Jean-Pat

4つのポイントを処理する必要がある場合は、それを行う最も簡単な方法があります

  1. y値で並べ替え

  2. 上の行は最初の2ポイント、下の行は残りの2ポイントです

  3. 上段と下段の場合は、x値で並べ替えます

corners.sort(key=lambda ii: ii[1], reverse=True)
topRow = corners[0:2]
bottomRow = corners[2:]

topRow.sort(key=lambda ii: ii[0])
bottomRow.sort(key=lambda ii: ii[0])
# clockwise
return [topRow[0], topRow[1], bottomRow[1], bottomRow[0]]
1
Eisneim

それを長い道のりで解決し、それを最適化します。

より具体的な問題は、正のx軸に対して角度を小さくして座標を並べ替えることです。この角度は、ラジアンで、次の関数によって与えられます。

x>0
    AND y >= 0
       angle = arctan(y/x)
    AND y < 0
       angle = arctan(y/x) + 2*pi
x==0
    AND y >= 0
       angle = 0
    AND y < 0
       angle = 3*pi/2
x<0
    angle = arctan(y/x) + pi

そして、もちろん、それは角度によって座標をソートするだけの問題です。 x> zの場合に限り、arctan(w)> arctan(z)であることに注意してください。これにより、角度を互いに非常に簡単に比較する関数を最適化できます。

角度がウィンドウ上で単調に減少するように(または最大で1回増加するように)ソートすることは少し異なります。

広範な証明の代わりに、1回のスワップ操作で4つの2Dポイントが時計回りにソートされることを確認したことを説明します。もちろん、どのスワップ操作が必要かを判断するのがコツです。

1
Wedge

以前の回答に追加するもう1つの改善点があります

覚えておいてください-これらは私たちが入ることができるケースです。

  1. あいうえお
  2. A B D C
  3. A C B D
  4. A C D B
  5. A D B C
  6. A D C B

ABCが反時計回り(負の符号付き領域がある)の場合、ケース3、4、6になります。この場合、BとCを交換すると、次の可能性が残ります。

  1. あいうえお
  2. A B D C
  3. あいうえお
  4. A B D C
  5. A D B C
  6. A D B C

次に、ABDをチェックし、反時計回りの場合はBとDを交換できます(ケース5、6)

  1. あいうえお
  2. A B D C
  3. あいうえお
  4. A B D C
  5. A B D C
  6. A B D C

最後に、ACDをチェックし、ACDが反時計回りの場合はCとDを交換する必要があります。これで、ポイントがすべて整っていることがわかりました。

この方法は、以前の方法ほど効率的ではありません。これには、毎回3回のチェックと、複数のスワップが必要です。しかし、コードははるかに単純になります。

1
Oliver Hallam
var arr = [{x:3,y:3},{x:4,y:1},{x:0,y:2},{x:5,y:2},{x:1,y:1}];
var reference = {x:2,y:2};
arr.sort(function(a,b)  {
    var aTanA = Math.atan2((a.y - reference.y),(a.x - reference.x));
    var aTanB = Math.atan2((b.y - reference.y),(b.x - reference.x));
    if (aTanA < aTanB) return -1;
    else if (aTanB < aTanA) return 1;
    return 0;
});
console.log(arr);

基準点がポリゴンの内側にある場所。

詳細はこちら サイト

1
Shekhar

これはどう?

// Take signed area of ABC.
// If negative,
//     Swap B and C.
// Otherwise,
//     Take signed area of ACD.
//     If negative, swap C and D.

アイデア?

0
Agnel Kurian

点xが点yよりも大きいと仮定すると、点(0,0)との角度が大きい場合、c#でこのように実装できます。

    class Point : IComparable<Point>
    {
        public int X { set; get; }
        public int Y { set; get; }

        public double Angle
        {
            get
            {
                return Math.Atan2(X, Y);
            }
        }

        #region IComparable<Point> Members

        public int CompareTo(Point other)
        {
            return this.Angle.CompareTo(other.Angle);
        }

        #endregion

        public static List<Point>  Sort(List<Point> points)
        {
            return points.Sort();
        }
}
0
Pablo Retyk
if AB crosses CD
   swap B,C
Elif AD crosses BC
   swap C,D

if area (ABC) > 0
   swap B,D

(I mean area(ABC) > 0 when A->B->C is counter-clockwise).
Let p*x + q*y + r = 0 be the straight line that joins A and B.
Then AB crosses CD if  p*Cx + q*Cy + r  and  p*Dx + q*Dy + r
have different sign, i.e. their product is negative.

最初の「if/Elif」は、4つのポイントを時計回りにまたは反時計回りの順序で移動します。 (ポリゴンは凸面であるため、他の「交差」の選択肢は「AC交差BD」のみです。これは、4つのポイントがすでにソートされていることを意味します。)最後の「if」は、反時計回りになると方向を反転します。

0

グラハムスキャンをご覧ください。もちろん、反時計回りのポイントを見つけるので、それを適応させる必要があります。

p.s:これは4ポイントではやり過ぎかもしれませんが、ポイント数が増えると面白いかもしれません

0
Julien Grenier

1回の交換で、平面内の4点で表されるポリゴンが凸状になることを保証できるのは正しいと思います。まだ答えられていない質問は次のとおりです。

  • この4点のセットは凸多角形ですか?
  • いいえの場合、どの2つのポイントを交換する必要がありますか?
  • 時計回りはどちらの方向ですか?

さらに考えてみると、上記の2番目の質問に対する唯一の答えは「真ん中の2つ」だと思います。

0
Greg Hewgill