按给定轴的角度对点进行排序?

Posted

技术标签:

【中文标题】按给定轴的角度对点进行排序?【英文标题】:Sort points by angle from given axis? 【发布时间】:2011-10-14 22:29:07 【问题描述】:

如何通过从给定轴向量逆时针增加角度对点/向量数组进行排序?

例如:

如果0 是轴向量,我希望排序后的数组按照2, 3, 1 的顺序排列。

我有理由确定可以使用交叉产品、自定义比较器和 std::sort() 来做到这一点。

【问题讨论】:

只是好奇,你的图片是在哪里制作的? 我假设你的意思是点积?这对我来说看起来是 2D 的。不过很难说。 我认为你至少不能使用点积,向量都必须是相同的长度,即使那样你也只能得到角度的余弦。 @Tomas:我编写的一个小 GLUT 和 OpenGL 测试工具程序。只需足够的鼠标处理逻辑即可添加点并拖动它们。帮助我轻松地制作原型/可视化 2D 矢量内容。 【参考方案1】:

是的,您可以使用基于叉积的自定义比较器来实现。唯一的问题是一个简单的比较器不具有传递性。因此需要一个额外的步骤,以防止参考任一侧的角度被视为接近。

这将比任何涉及 trig 的方法快得多。甚至都不需要先标准化。

这是比较器:

class angle_sort

    point m_origin;
    point m_dreference;

    // z-coordinate of cross-product, aka determinant
    static double xp(point a, point b)  return a.x * b.y - a.y * b.x; 
public:
    angle_sort(const point origin, const point reference) : m_origin(origin), m_dreference(reference - origin) 
    bool operator()(const point a, const point b) const
    
        const point da = a - m_origin, db = b - m_origin;
        const double detb = xp(m_dreference, db);

        // nothing is less than zero degrees
        if (detb == 0 && db.x * m_dreference.x + db.y * m_dreference.y >= 0) return false;

        const double deta = xp(m_dreference, da);

        // zero degrees is less than anything else
        if (deta == 0 && da.x * m_dreference.x + da.y * m_dreference.y >= 0) return true;

        if (deta * detb >= 0) 
            // both on same side of reference, compare to each other
            return xp(da, db) > 0;
        

        // vectors "less than" zero degrees are actually large, near 2 pi
        return deta > 0;
    
;

演示:http://ideone.com/YjmaN

【讨论】:

太棒了!但是,“==0”逻辑是不正确的,因为角度可能是 0 或 180 度。修复:if ( aIs0or180 && bIs0or180 ) return aIs0 && bIs180;if ( aIs0or180 ) return aIs0 || detb<0;if ( bIs0or180 ) return bIs180 && deta>0; 其中 aIs0or180 = deta==0, aIs0 = dot(m_dreference,da) > 0 等。所有这些特殊情况逻辑都可以包装在 if (deta*detb==0) 这真的很棒,我让它为 java 工作。我只是想知道,在java中我应该返回-1、0或1,其中0返回等于。现在我应该在哪里为这个角度排序返回 0? @clankill3r: 如果lessthan 是上面的函数,那么lessthan(a,b) - lessthan(b,a) 会给你-1, 0, 1 的结果集。但是你不想重复叉积,所以实际上将它实现为两个调用不如编写一个真正的“比较”函数。【参考方案2】:

最直接但可能不是最佳的方法是将笛卡尔坐标移动到相对于中心点,然后convert them to polar coordinates。然后只需减去“起始向量”模360的角度,最后按角度排序。

或者,您可以制作一个自定义比较器来处理所有可能的斜率和配置,但我认为极坐标更透明一些。

【讨论】:

@JohnYang,你当然会以 360 为模。我编辑了我的答案以更清楚。【参考方案3】:
#include <iostream>
#include <cmath>
#include <algorithm>

using namespace std;

struct Point 
    static double base_angle;
    static void set_base_angle(double angle)
        base_angle = angle;
    
    double x;
    double y;
    Point(double x, double y):x(x),y(y)
    double Angle(Point o = Point(0.0, 0.0))
        double dx = x - o.x;
        double dy = y - o.y;
        double r = sqrt(dx * dx + dy * dy);
        double angle = atan2(dy , dx);
        angle -= base_angle;
        if(angle < 0) angle += M_PI * 2;
        return angle;
    
;
double Point::base_angle = 0;

ostream& operator<<(ostream& os, Point& p)
    return os << "Point(" << p.x << "," << p.y << ")";


bool comp(Point a, Point b)
    return a.Angle() < b.Angle();


int main()
    Point p[] =  Point(-4., -4.), Point(-6., 3.), Point(2., -4.), Point(1., 5.) ;
    Point::set_base_angle(p[0].Angle());
    sort(p, p + 4, comp);
    Point::set_base_angle(0.0);
    for(int i = 0;i< 4;++i)
        cout << p[i] << " angle:" << p[i].Angle() << endl;
    

演示

Point(-4,-4) angle:3.92699
Point(2,-4) angle:5.17604
Point(1,5) angle:1.3734
Point(-6,3) angle:2.67795

【讨论】:

【参考方案4】:

假设它们的长度相同且来源相同,则可以排序

struct sorter  
    operator()(point a, point b) const   
        if (a.y > 0)  //a between 0 and 180
            if (b.y < 0)  //b between 180 and 360
                return false;
            return a.x < b.x; 
         else  // a between 180 and 360
            if (b.y > 0) //b between 0 and 180
                return true;
            return a.x > b.x;
        
    
    //for comparison you don't need exact angles, simply relative.

这将快速从 0->360 度对它们进行排序。然后你找到你的向量0(在位置N),并且std::rotate结果留下了N个元素。 (感谢 TomSirgedas!)

【讨论】:

我以为有一个实现,但我很傻,我懒得看。 我在前三个比较中需要&gt;=/&lt;=/&lt;=,否则std::sort() 抱怨operator&lt; 无效,如果其中一个向量位于+x 轴上。 【参考方案5】:

这是我如何解决这个问题的一个例子。它转换为极坐标以获得角度,然后用于比较它们。您应该可以在这样的排序函数中使用它:

std::sort(vectors.begin(), vectors.end(), VectorComp(centerPoint));

下面是比较代码

struct VectorComp : std::binary_function<sf::Vector2f, sf::Vector2f, bool>


    sf::Vector2f M;
    IntersectComp(sf::Vector2f v) : M(v) 

    bool operator() ( sf::Vector2f o1,  sf::Vector2f o2)
    
        float ang1     = atan( ((o1.y - M.y)/(o1.x - M.x) ) * M_PI / 180);
        float ang2     = atan( (o2.y - M.y)/(o2.x - M.x) * M_PI / 180);
        if(ang1 < ang2) return true;
        else if (ang1 > ang2) return false;
        return true;
    
;

它使用 sfml 库,但您可以切换任何矢量/点类而不是 sf::Vector2f。 M 将是中心点。如果您想绘制某种三角扇,它会非常有用。

【讨论】:

【参考方案6】:

你应该首先对每个向量进行归一化,所以每个点都是 (cos(t_n), sin(t_n)) 格式。 然后计算每个点与参考点之间角度的 cossin。当然:

cos(t_n-t_0)=cos(t_n)cos(t_0)+sin(t_n)sin(t_0)  (this is equivalent to dot product)
sin(t_n-t_0)=sin(t_n)cos(t_0)-cos(t_n)sin(t_0)

仅基于这两个值,您可以确定点和参考点之间的精确角度(-pi 到 pi)。如果仅使用点积,则相同角度的顺时针和逆时针具有相同的值。一个你确定角度,对它们进行排序。

【讨论】:

【参考方案7】:

我知道这个问题已经很老了,并且接受的答案帮助我解决了这个问题,但我认为我有一个更优雅的解决方案,它也涵盖了平等(所以返回 -1 代表 lowerThan,0 代表等于,1 代表大于)。

它是基于将平面分成2半,一个从正参考轴(包括)到负参考轴(不包括),另一个是它的补码。

在每一半内,可以通过右手定则(叉积符号)进行比较,或者换句话说 - 两个向量之间角度的正弦符号。 如果这 2 个点来自不同的一半,那么比较是微不足道的,并且是在两半之间进行的。

为了获得足够均匀的分布,这个测试应该平均执行 4 次比较、1 次减法和 1 次乘法,除了用 ref 完成的 4 次减法之外,我认为应该预先计算。

int compareAngles(Point const & A, Point const & B, Point const & ref = Point(0,0)) 
  typedef decltype(Point::x) T;  // for generality. this would not appear in real code.
  const T sinA = A.y - ref.y; // |A-ref|.sin(angle between A and positive ref-axis)
  const T sinB = B.y - ref.y; // |B-ref|.sin(angle between B and positive ref-axis)
  const T cosA = A.x - ref.x; // |A-ref|.cos(angle between A and positive ref-axis)
  const T cosB = B.x - ref.x; // |B-ref|.cos(angle between B and positive ref-axis)

  bool hA = ( (sinA < 0) || ((sinA == 0) && (cosA < 0)) ); // 0 for [0,180). 1 for [180,360).
  bool hB = ( (sinB < 0) || ((sinB == 0) && (cosB < 0)) ); // 0 for [0,180). 1 for [180,360).

  if (hA == hB) 
    // |A-ref|.|B-ref|.sin(angle going from (B-ref) to (A-ref))
    T sinBA = sinA * cosB - sinB * cosA;
    // if T is int, or return value is changed to T, it can be just "return sinBA;"
    return ((sinBA > 0) ? 1 : ((sinBA < 0) ? (-1) : 0));
  
  return (hA - hB);

【讨论】:

【参考方案8】:

如果S是PointF的数组,mid是中心的PointF:

S = S.OrderBy(s => -Math.Atan2((s.Y - mid.Y), (s.X - mid.X))).ToArray();

将按照围绕中点的旋转顺序对列表进行排序,从最接近 (-inf,0) 的点开始,然后逆时针方向(如果您在 Math 之前省略负号,则顺时针方向)。

【讨论】:

以上是关于按给定轴的角度对点进行排序?的主要内容,如果未能解决你的问题,请参考以下文章

数据结构和算法——快速排序

在Java中按极角对点进行排序

ggplot2按y轴的比例排序分类堆积条

用Java对点列表进行排序[重复]

PrimeNg 表未按角度对类数组进行排序

jQuery - 按时间顺序对任何给定的 JSON 进行排序