按顺序排序四点

时间:2008-10-28 06:36:25

标签: algorithm graphics sorting geometry

阵列中的四个2D点。我需要按顺时针顺序对它们进行排序。我认为只需一次交换操作即可完成,但我无法正式解决这个问题。

编辑:在我的情况下,四个点是凸多边形。

编辑:四个点是凸多边形的顶点。他们不需要整理好。

16 个答案:

答案 0 :(得分:18)

如果你想采用更多的数学观点,我们可以考虑4点的排列

在我们的案例中,有4种顺序顺序排列

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

所有其他可能的排列都可以通过0或1交换转换为其中一种形式。 (我只考虑以A开头的排列,因为它是对称的)

  1. A B C D - 完成
  2. A B D C - 交换C和D
  3. A C B D - 交换B和C
  4. A C D B - 交换A和B
  5. A D B C - 交换A和D
  6. A D C B - 交换B和D
  7. 因此,只需要一次交换 - 但可能需要一些工作才能确定哪个。

    通过查看前三个点并检查ABC签名区域的符号,我们可以确定它们是否顺时针方向。如果它们是顺时针方向,那么我们就是1 2或5

    要区分这些情况,我们必须检查另外两个三角形 - 如果ACD是顺时针方向,那么我们可以将其缩小到情况1,否则我们必须在2或5的情况下。

    要在案例2和案例5之间进行选择,我们可以测试ABD

    我们可以类似地检查ABC逆时针的情况。

    在最坏的情况下,我们必须测试3个三角形。

    如果你的点不是凸面,你会找到内侧点,对其余点进行排序,然后将其添加到任何边缘。请注意,如果四边形是凸的,那么4个点不再唯一地确定四边形,则有3个同样有效的四边形。

答案 1 :(得分:6)

这里有一些值得考虑的想法;

  • 顺时针方向仅对原点有意义。我倾向于将原点视为一组点的重心。例如顺时针相对于四个点的平均位置的点,而不是可能非常遥远的原点。

  • 如果您有四个点,a,b,c,d,则原点周围存在多个顺时针顺序。例如,如果(a,b,c,d)形成顺时针排序,那么(b,c,d,a),(c,d,a,b)和(d,a,b,c)< / p>

  • 您的四个点是否已形成多边形?如果是这样,则需要检查和反转绕组而不是对点进行分类,例如, a,b,c,d变为d,c,b,a。如果没有,我会根据每个点和原点之间的连接进行排序,根据Wedges响应。

编辑,关于您对要交换的点的评论;

在三角形(a,b,c)的情况下,我们可以说它是顺时针方向,如果第三个点 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);
}

如果我有一个四点凸多边形,(a,b,c,d),我可以将其视为两个三角形,(a,b,c)和(c,d,a)。如果(a,b,c)是逆时针方向,我将绕组(a,b,c,d)更改为(a,d,c,b),以将多边形的绕组整体改为顺时针方向。

我强烈建议用几个样本点来绘制,看看我在说什么。请注意,您需要处理许多异常情况,例如凹面多边形,共线点,重合点等等。

答案 2 :(得分:3)

奥利弗是对的。此代码(社区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 :(得分:3)

如果有人感兴趣,这是我对类似问题的快速而肮脏的解决方案。

我的问题是按以下顺序排列矩形角:

  

左上角&gt;右上角&gt;右下&gt;左下

基本上它是从左上角开始的顺时针顺序。

算法的想法是:

按行对角进行排序,然后按cols对角对进行排序。

// 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;
}

答案 4 :(得分:2)

对于每个点排列,用鞋带公式(绝对值的除数使得该区域可以是正的或负的)计算坐标的面积。 最大面积值似乎对应于直接的简单四边形:Simple direct quadrilaterals found with the shoelace formula

enter image description here

答案 5 :(得分:1)

长期研究然后优化它。

更具体的问题是通过相对于正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

然后,当然,这只是按角度对坐标进行排序的问题。注意arctan(w)&gt; arctan(z)当且仅当x> z,所以你可以很容易地优化一个比较角度的函数。

排序使得角度在窗口上单调递减(或者它最多增加一次)有点不同。

代替一个广泛的证据我会提到我确认单个交换操作将按顺时针顺序对4个2D点进行排序。当然,确定哪种交换操作是必要的。

答案 6 :(得分:1)

我还有一项改进,可以添加到我以前的答案中

记住 - 这些是我们可以参与的案例。

  1. A B C D
  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
  7. 如果ABC逆时针(具有负面签名区域),那么我们处于3,4,6的情况。如果我们交换B&amp;在这种情况下,我们留下了以下可能性:

    1. A B C D
    2. A B D C
    3. A B C D
    4. A B D C
    5. A D B C
    6. A D B C
    7. 接下来我们可以检查ABD并交换B&amp; D如果是逆时针方向(案例5,6)

      1. A B C D
      2. A B D C
      3. A B C D
      4. A B D C
      5. A B D C
      6. A B D C
      7. 最后我们需要检查ACD并交换C&amp; D如果ACD是逆时针方向的话。现在我们知道我们的观点都是有序的。

        这种方法效率不如我以前的方法 - 每次需要3次检查,并且需要多次交换;但代码会简单得多。

答案 7 :(得分:1)

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);

参考点位于多边形内部。

site

的更多信息

答案 8 :(得分:1)

如果你只需要处理4分,那么最简单的方法就是

  1. 按y值排序

  2. 顶行是前两个点,底行是剩下的2个点

  3. 对于顶行和底行,按x值排序

  4. 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]]
    

答案 9 :(得分:0)

我相信你是对的,单个交换可以确保由平面中的四个点表示的多边形是凸的。还有待回答的问题是:

  • 这组四个点是凸多边形吗?
  • 如果不是,那么需要交换哪两个点?
  • 顺时针方向是哪个方向?

经过进一步反思,我认为上面第二个问题的唯一答案是“中间两个”。

答案 10 :(得分:0)

这个怎么样?

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

想法?

答案 11 :(得分:0)

如果我们假设点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();
        }
}

答案 12 :(得分:0)

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'以顺时针方向逆时针方向显示四个点。 (由于你的多边形是凸的,唯一的另一个'交叉'替代是'AC穿过BD',这意味着这四个点已经被排序了。) 无论逆时针方向,最后一个'if'反转方向。

答案 13 :(得分:0)

你应该看看格雷厄姆的扫描。 当然,你需要调整它,因为它发现逆时针点。

p.s:4分可能有点过分但如果分数增加可能会很有意思

答案 14 :(得分:-1)

Wedge的回答是正确的。

为了轻松实现它,我认为与smacl相同:你需要找到边界的中心并将你的点转换到那个中心。

像这样:

centerPonintX = Min(x) + (  (Max(x) – Min(x)) / 2  )
centerPonintY = Min(y) + (  (Max(y) – Min(y)) / 2  )

然后,从每个点减少centerPointX和centerPointY,以便将其转换为边界的原点。

最后,仅使用一个扭曲来应用Wedge的解决方案:获取每个实例的arctan(x / y)的绝对值(以这种方式为我工作)。

答案 15 :(得分:-1)

if( (p2.x-p1.x)*(p3.y-p1.y) > (p3.x-p1.x)*(p2.y-p1.y) )
    swap( &p1, &p3 );

'&gt;'可能面临错误的方式,但你明白了。