数组中的四个二维点。我需要按顺时针顺序对它们进行排序。我认为只需一次交换操作即可完成,但我无法正式将其放下。
编辑:在我的例子中,这四个点是一个凸多边形。
编辑:这四个点是凸多边形的顶点。他们不必井井有条。
如果你想从更数学的角度来看,我们可以考虑 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);
}
如果我有一个四点凸多边形 (a,b,c,d),我可以将其视为两个三角形 (a,b,c) 和 (c,d,a)。如果 (a,b,c) 是逆时针方向,我将绕组 (a,b,c,d) 更改为 (a,d,c,b) 以将整个多边形的绕组更改为顺时针方向。
我强烈建议用几个样本点画这个,看看我在说什么。请注意,您有很多例外情况需要处理,例如凹多边形、共线点、重合点等...
如果有人感兴趣,这是我对类似问题的快速而肮脏的解决方案。
我的问题是按以下顺序排列我的矩形角:
左上>右上>右下>左下
基本上它是从左上角开始的顺时针顺序。
该算法的思想是:
按行对角进行排序,然后按列对角对进行排序。
// 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 点数组的所有可能组合。
#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;
}
对于每个点排列,使用鞋带公式从坐标计算面积(没有绝对值,因此面积可以为正或负)。最大面积值似乎对应于直接简单四边形:使用鞋带公式找到的简单直接四边形
解决它很长的路要走,然后优化它。
一个更具体的问题是通过减小相对于正 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 点进行排序。当然,确定需要哪个交换操作是诀窍。
我有一个进一步的改进要添加到我之前的答案中
记住 - 这些是我们可以遇到的情况。
如果 ABC 是逆时针方向(有负符号区域),那么我们处于第 3、4、6 种情况。如果我们在这种情况下交换 B 和 C,则剩下以下可能性:
接下来我们可以检查 ABD 并在逆时针方向交换 B 和 D(案例 5、6)
最后,我们需要检查 ACD,如果 ACD 是逆时针方向,则交换 C & D。现在我们知道我们的观点都是有序的。
这种方法不如我之前的方法效率高——这需要每次检查 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);
参考点位于多边形内的位置。
更多信息在这个网站
如果您只需要处理 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]]
我相信您是对的,单次交换可以确保由平面中的四个点表示的多边形是凸的。仍有待回答的问题是:
进一步思考,我认为上面第二个问题的唯一答案是“中间两个”。
这个怎么样?
// Take signed area of ABC.
// If negative,
// Swap B and C.
// Otherwise,
// Take signed area of ACD.
// If negative, swap C and D.
想法?
如果我们假设点 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();
}
}
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”在逆时针方向反转。
你应该看看格雷厄姆的扫描。当然,您需要对其进行调整,因为它会逆时针找到指向。
ps:这对于4分来说可能有点过分了,但是如果点数增加它可能会很有趣
Wedge 的回答是正确的。
为了轻松实现它,我认为与 smacl 相同:您需要找到边界的中心并将您的点转换到该中心。
像这样:
centerPonintX = Min(x) + ( (Max(x) – Min(x)) / 2 )
centerPonintY = Min(y) + ( (Max(y) – Min(y)) / 2 )
然后,从每个点减少 centerPointX 和 centerPointY 以将其转换为边界的原点。
最后,只需一个转折即可应用 Wedge 的解决方案:获取每个实例的 arctan(x/y) 的绝对值(这样对我有用)。
if( (p2.x-p1.x)*(p3.y-p1.y) > (p3.x-p1.x)*(p2.y-p1.y) )
swap( &p1, &p3 );
'>' 可能面对错误的方向,但你明白了。