【问题标题】:Sort Four Points in Clockwise Order按顺时针顺序排列四个点
【发布时间】:2010-09-19 12:51:08
【问题描述】:

数组中的四个二维点。我需要按顺时针顺序对它们进行排序。我认为只需一次交换操作即可完成,但我无法正式将其放下。

编辑:在我的例子中,这四个点是一个凸多边形。

编辑:四个点是凸多边形的顶点。它们不需要按顺序排列。

【问题讨论】:

  • 你的意思是顺时针绕原点吗?
  • 也可以顺时针绕着键中心。
  • 对我来说,凸多边形的顺时针顺序意味着您只在遍历点时进行右转。起源应该无关紧要。
  • 如果我们谈论的是顺时针多边形,请检查我关于分解为两个三角形并在此基础上交换的编辑。这是一个更简单的命题。

标签: algorithm graphics sorting geometry


【解决方案1】:

如果你只需要处理 4 个点,那么有一个最简单的方法来做到这一点

  1. 按y值排序

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

【讨论】:

    【解决方案2】:

    奥利弗是对的。此代码(社区维基化)生成并排序 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;
    }
    

    【讨论】:

    • 将有符号面积计算分离为一个函数将提高可读性。如果性能很重要,那么您可以提取所有表达式,例如 (a.xc.y - c.xa.y) - 您最终会使用它们两次。
    【解决方案3】:
    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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

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

        左上角 > 右上角 > 右下角 > 左下角

        基本上是从左上角开始顺时针排列。

        算法的思路是:

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

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

        【讨论】:

          【解决方案6】:

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

          【讨论】:

          • 请注意,您为 (1, 1) 和 (-1, -1) 计算相同的角度。你想要 Math.Atan2(x, y)
          【解决方案7】:

          如果你想从更数学的角度来看,我们可以考虑 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

          因此只需要一次交换 - 但可能需要一些工作才能确定哪个。

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

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

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

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

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

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

          【讨论】:

          • 你能解释一下“ABC的签名区域”是什么意思吗?
          【解决方案8】:

          我还有一个改进要添加到我之前的答案中

          记住 - 这些是我们可能遇到的情况。

          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

          如果 ABC 是逆时针方向(有负符号区域),那么我们处于第 3、4、6 种情况。如果我们在这种情况下交换 B 和 C,则剩下以下可能性:

          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

          接下来我们可以检查 ABD 并在逆时针方向交换 B & 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

          最后,我们需要检查 ACD,如果 ACD 是逆时针方向,则交换 C & D。现在我们知道我们的观点都井然有序。

          这种方法不如我之前的方法高效——这需要每次检查 3 次,并且需要多次交换;但是代码会简单很多。

          【讨论】:

            【解决方案9】:
            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”在逆时针方向反转。

            【讨论】:

            • 附注一次交换不足以将四个点按顺时针顺序排列。这就像假装只用一次交换按升序对三个数字进行排序(3 1 2,你会做什么?)。
            • 错了。在数字示例中:1、2 和 3 具有固定的最终位置。在顺时针 4 点的情况下,我们可以有 ABCD、BCDA、CDAB 和 DABC。
            • 好吧,我错了,但不是因为你提到的原因。我的想法是这样的:为了给 A123 排序 A312(只是对 ADBC 的重新标记)与按升序排序 312 相同。这在某种意义上是正确的,但在这里无关紧要,因为在我们的设置中,我也可以移动 A。
            • 无论如何,为什么要对掉期大惊小怪?交换确实是一种快速操作,我宁愿计算 2 个区域并进行 2 次交换,而不是计算 3 个区域,以便选择唯一合适的交换来发挥作用。
            【解决方案10】:

            您应该看看 Graham's Scan。 当然,您需要调整它,因为它会逆时针找到指向。

            p.s:这对于 4 分来说可能有点矫枉过正,但如果点数增加它可能会很有趣

            【讨论】:

              【解决方案11】:
              if( (p2.x-p1.x)*(p3.y-p1.y) > (p3.x-p1.x)*(p2.y-p1.y) )
                  swap( &p1, &p3 );
              

              '>' 可能朝错误的方向,但你明白了。

              【讨论】:

                【解决方案12】:

                这里有几个值得考虑的想法;

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

                • 如果您有四个点 a、b、c、d,则这些点在您的原点周围存在多个顺时针顺序。例如,如果 (a,b,c,d) 形成顺时针顺序,则 (b,c,d,a)、(c,d,a,b) 和 (d,a,b,c)

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

                编辑:关于您的 cmets 交换哪些点;

                对于三角形 (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) 以将整个多边形的绕组更改为顺时针方向。

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

                【讨论】:

                • 这仅在您将其分割成的两个三角形不相交时才有效。请参阅我的帖子中的第二张图 - ABC 和 CDA 相互交叉,因此使它们顺时针并不能解决整个问题
                • 我认为 (a,b,c,d) 是一组有序的顶点或循环,因此多边形的边是 ab,bc,cd 和 da。我认为这是合理的,因为开头的问题与凸多边形有关,这意味着顶点的排序。
                • 我的意思是这些点是凸多边形的顶点。我并不是说它们是有序的。
                【解决方案13】:

                Wedge 的回答是正确的。

                为了容易实现,我想和 smacl 一样:你需要找到边界的中心,然后将你的点转换到那个中心。

                像这样:

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

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

                最后,只需一个转折即可应用 Wedge 的解决方案:获取每个实例的 arctan(x/y) 的绝对值(对我有用)。

                【讨论】:

                  【解决方案14】:

                  长期解决然后优化它。

                  一个更具体的问题是通过减少相对于正 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),因此您可以优化一个非常容易地比较角度的函数。

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

                  我将提到我验证了单个交换操作将按顺时针顺序对 4 个 2D 点进行排序,而不是广泛的证明。当然,确定需要哪个交换操作是诀窍。

                  【讨论】:

                  • “作为一个广泛的证明,我将提到我验证了单个交换操作将按顺时针顺序对 4 个 2D 点进行排序。当然,确定哪个交换操作是必要的。 "你是如何验证的?
                  • @Vulcan 通过检查,排序 4 个值的可能方法只有这么多,我列出了所有这些方法,然后确定如何以所需的方式对它们进行排序,每种情况只有一个交换操作需要。你可以自己做,写下所有数字 1,2,3,4 的排序方式。
                  • 这会将点排列在原点周围顺时针方向,而不是顺时针四边形。加上 arctan (相对)慢。
                  • 请注意,在 .NET Math.Atan2(x, y) 中计算您的角度时不会有任何混乱。
                  • @Oliver 好吧,真正的目标是创建一个完全不使用 atan 的优化解决方案。
                  【解决方案15】:

                  这个怎么样?

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

                  想法?

                  【讨论】:

                  • 交换对面的顶点而不是相邻的顶点。但除此之外,是的。
                  • PS:如果 ABC 的符号面积为零(或在它的某个 epsilon 之内),您只需要进行第二次测试。
                  【解决方案16】:

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

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

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

                  【讨论】:

                  • -1 因为你对第二个问题的回答是错误的,除非你假设这些点是顺时针或逆时针的(考虑我下面给出的例子)。
                  猜你喜欢
                  • 2011-10-22
                  • 2018-06-05
                  • 2018-12-07
                  • 1970-01-01
                  • 2011-10-16
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多