Сортировка четырех точек по часовой стрелке

Четыре двумерных точки в массиве. Мне нужно отсортировать их по часовой стрелке. Я думаю, что это может быть сделано только с одной операцией свопинга, но я не смог официально ее отложить.

Изменить: четыре точки - это выпуклый многоугольник в моем случае.

Изменить: четыре точки - это вершины выпуклого многоугольника. Они не должны быть в порядке.

Ответ 1

Если вы хотите взять более математическую перспективу, мы можем рассмотреть перестановки 4 точек

В нашем случае есть 4 перестановки, которые по часовой стрелке

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

Все остальные возможные перестановки могут быть преобразованы в одну из этих форм с 0 или 1 свопами. (Я буду рассматривать только перестановки, начинающиеся с A, так как они симметричны)

  • A B C D - done
  • A B D C - замена C и D
  • A C B D - замена B и C
  • A C D B - свопинг A и B
  • A D B C - замена A и D
  • A D C B - замена B и D

Таким образом, требуется только один обмен, но может потребоваться некоторая работа, чтобы определить, какой из них.

Изучая первые три точки и проверяя знак подписанной области ABC, мы можем определить, являются ли они по часовой стрелке или нет. Если они по часовой стрелке, то мы в случае 1 2 или 5

чтобы различать эти случаи, мы должны проверить еще два треугольника - если ACD по часовой стрелке, мы можем сузить это до случая 1, иначе мы должны быть в случае 2 или 5.

Чтобы выбрать между случаями 2 и 5, мы можем проверить ABD

Аналогично мы можем проверить случай ABC против часовой стрелки.

В худшем случае нам нужно проверить три треугольника.

Если ваши точки не выпуклые, вы найдете внутреннюю точку, отсортируйте остальную часть и затем добавьте ее в любое ребро. Заметим, что если квад выпуклый, то четыре точки больше не однозначно определяют квад, то есть 3 одинаково допустимых квадратика.

Ответ 2

Несколько мыслей стоит рассмотреть здесь;

  • По часовой стрелке имеет смысл только относительно происхождения. Я бы подумал о происхождении как о центре тяжести множества точек. например По часовой стрелке относительно точки в среднем положении четырех точек, а не, возможно, очень отдаленного начала.

  • Если у вас есть четыре точки, 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.

Изменить: относительно ваших комментариев о том, какие точки для обмена;

В случае треугольника (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), чтобы изменить обмотку многоугольника в целом по часовой стрелке.

Я настоятельно рекомендую нарисовать это несколькими примерами, чтобы посмотреть, о чем я говорю. Обратите внимание, что у вас есть много исключительных случаев, таких как вогнутые полигоны, коллинеарные точки, совпадающие точки и т.д.

Ответ 3

Если кому-то интересно, вот мое быстрое и грязное решение аналогичной проблемы.

Моя проблема заключалась в том, чтобы упорядочить мои прямоугольные углы в следующем порядке:

верхний левый > верхний правый > нижний правый > нижний левый

В основном это порядок по часовой стрелке, начиная с верхнего левого угла.

Идея алгоритма:

упорядочить углы по строкам, а затем упорядочить угловые пары по столбцам.

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

Оливер прав. Этот код (сообщество 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;
}

Ответ 5

Вычислите область из координат с помощью формулы шнура (лишенной абсолютного значения, чтобы площадь была положительной или отрицательной) для каждой перестановки точек. Максимальные значения площади, по-видимому, соответствуют прямым простым четырехугольникам: Простые прямые четырехугольники, найденные с формулой шнурка

enter image description here

Ответ 6

Проработайте это долго, затем оптимизируйте его.

Более конкретная проблема заключалась бы в сортировке координат путем уменьшения угла относительно положительной оси х. Этот угол в радианах будет определяться этой функцией:

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) > arctan (z) тогда и только тогда, когда x > z, поэтому вы можете оптимизировать функцию, которая легко сравнивает углы друг с другом.

Сортировка таким образом, что угол монотонно уменьшается по окну (или такой, что он увеличивается не более одного раза) немного отличается.

Вместо обширного доказательства я упомянул, что я проверил, что одна операция свопинга будет сортировать 4 2D-точки по часовой стрелке. Разумеется, определение того, какая операция подкачки необходима, - это трюк.

Ответ 7

У меня есть еще одно улучшение, чтобы добавить к моему предыдущему ответу

помните - это те случаи, в которых мы можем быть.

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

Если ABC против часовой стрелки (имеет отрицательную подписанную область), то мы находимся в случаях 3, 4, 6. Если в этом случае мы заменим B и C, то мы оставим следующие возможности:

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

Далее мы можем проверить ABD и поменять B и D, если он против часовой стрелки (случаи 5, 6)

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

Наконец, нам нужно проверить ACD и сменить C и D, если ACD против часовой стрелки. Теперь мы знаем, что наши пункты все в порядке.

Этот метод не так эффективен, как мой предыдущий метод - это требует 3 проверки каждый раз и более одного свопа; но код будет намного проще.

Ответ 8

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

Если опорная точка лежит внутри многоугольника.

Дополнительная информация на этом сайте

Ответ 9

Я считаю, что вы правы, что один обмен может гарантировать, что многоугольник, представленный четырьмя точками на плоскости, выпуклый. На вопросы, на которые еще предстоит ответить, входят:

  • Является ли этот набор из четырех точек выпуклым многоугольником?
  • Если нет, то какие две точки нужно поменять местами?
  • Какое направление по часовой стрелке?

После дальнейшего размышления я считаю, что единственный ответ на второй вопрос выше - "средний два".

Ответ 10

Как насчет этого?

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

Идеи?

Ответ 11

если предположить, что точка x больше точки y, если угол, который он имеет с точкой (0,0), больше, то мы можем реализовать это таким образом в С#

    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

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 crosses BD", что означает, что четыре точки уже отсортированы.) Последняя "if" инвертирует ориентацию всякий раз, когда она против часовой стрелки.

Ответ 13

Вы должны взглянуть на Graham Scan. Конечно, вам нужно будет адаптировать его, поскольку он находит точки против часовой стрелки.

p.s: Это может быть излишним для 4 очков, но если количество точек увеличится, это может быть интересно

Ответ 14

если вам просто нужно иметь дело с 4 очками, тогда есть самый простой способ сделать это

  • сортировать по значению y

  • верхняя строка - это первые две точки, нижняя строка - остальные 2 точки

  • для верхней и нижней строки, сортируйте их по значению 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]]

Ответ 15

Wedges Ответ правильный.

Чтобы реализовать это легко, я думаю так же, как smacl: вам нужно найти центр границ и перевести свои точки в этот центр.

Вот так:

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

Затем уменьшите centerPointX и centerPointY от каждого poin, чтобы перевести его в начало координат.

Наконец, примените решение Wedges с помощью всего лишь одного поворота. Получите абсолютное значение arctan (x/y) для каждого экземпляра (работая для меня таким образом).

Ответ 16

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

" > " может оказаться неверным, но вы получите эту идею.