一个包含四个二维点的数组。我需要按顺时针顺序对它们进行排序。我认为只需要一次交换操作就能完成,但我还没有能够正式地写下来。
编辑:在我的情况下,这四个点是一个凸多边形。
编辑:这四个点是凸多边形的顶点。它们不一定是有序的。
一个包含四个二维点的数组。我需要按顺时针顺序对它们进行排序。我认为只需要一次交换操作就能完成,但我还没有能够正式地写下来。
编辑:在我的情况下,这四个点是一个凸多边形。
编辑:这四个点是凸多边形的顶点。它们不一定是有序的。
如果你想从更数学的角度来看,我们可以考虑4个点的排列。
在我们的例子中,有4个以顺时针顺序排列的排列。
A B C D
B C D A
C D A B
D A B C
所有其他可能的排列都可以通过0或1次交换转换为这些形式之一。(我只考虑以A开头的排列,因为它是对称的)
因此,只需要一次交换 - 但是可能需要一些工作来确定交换哪一个。
通过查看前三个点,并检查ABC的有向面积的符号,我们可以确定它们是否顺时针。如果它们顺时针,则我们在情况1、2或5中
为了区分这些情况,我们必须检查另外两个三角形 - 如果ACD是顺时针,则我们可以将其缩小到情况1,否则我们必须处于情况2或5。
要在情况2和5之间选择,我们可以测试ABD。
我们可以类似地检查ABC逆时针的情况。
在最坏的情况下,我们必须测试3个三角形。
如果您的点不是凸的,则会找到内部点,对其余点进行排序,然后将其添加到任何一条边上。请注意,如果四边形是凸的,则4个点不再唯一确定四边形,有3个同样有效的四边形。
这里有几个值得考虑的想法:
顺时针只有在参照点的情况下才有意义。我倾向于将参照点视为一组点的重心,例如相对于四个点的平均位置处的点的顺时针方向,而不是可能非常远的原点。
如果您有四个点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);
}
// 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;
}
orderPointsByRows
函数中,应该是 if (p1.y > p2.y) return -1; if (p1.y < p2.y) return 1;
吗?
你想要更大的(顶部)y值排在前面。 - Yoav Gur奥利弗是正确的。这段代码(社区维基)生成并排序了由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;
}
我有一个进一步的改进要添加到我的先前答案中
记住 - 这些是我们可能遇到的情况。
如果ABC是逆时针的(具有负面积),那么我们处于第3、4、6种情况。如果我们在这种情况下交换B和C,那么我们就只剩下以下可能性:
接下来,我们可以检查ABD并在其逆时针时交换B和D(第5、6种情况)
最后,我们需要检查ACD并在ACD逆时针时交换C和D。现在我们知道我们的点都是有序的。
这种方法不如我的先前的方法高效 - 每次需要3个检查和多个交换; 但代码会简单得多。
按y值排序
第一行为前两个点,第二行为后两个点
对于第一行和第二行,按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]]
先用较长的方式解决问题,然后再进行优化。
更具体的问题是按照相对于正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点。当然,确定哪个交换操作是必要的是诀窍。
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);
参考点位于多边形内部。
更多信息请访问网站。
这个怎么样?
// Take signed area of ABC.
// If negative,
// Swap B and C.
// Otherwise,
// Take signed area of ACD.
// If negative, swap C and D.
Ideas?