有效地将线段排序成循环

时间:2013-01-10 06:06:41

标签: javascript algorithm

我使用了一个库(JavaScript-Voronoi),它产生了一个代表闭合多边形的线段数组。这些段看起来是无序的,包括段出现的顺序以及段的每一端的点的排序。

修改:如下面的评论中所述,我错了:库中的细分排序良好。但是,问题代表如下:让我们假设这些细分没有任何排序,因为这使它更有用。)

例如:

var p1 = {x:13.6,y:13.1}, p2 = {x:37.2,y:35.8}, p3 = {x:99.9,y:14.6},
    p4 = {x:99.9,y:45.5}, p5 = {x:33.7,y:66.7};
var segments = [
  { va:p1, vb:p2 },
  { va:p3, vb:p4 },
  { va:p5, vb:p4 },
  { va:p3, vb:p2 },
  { va:p1, vb:p5 } ];

注意第一个段如何链接到最后一个段(它们共享一个公共点),以及倒数第二个段。保证每个段与另一个段完全共享。

我想将其转换为点列表以生成正确的SVG多边形:

console.log( orderedPoints(segments) );
// [
//   {"x":33.7,"y":66.7},
//   {"x":13.6,"y":13.1},
//   {"x":37.2,"y":35.8},
//   {"x":99.9,"y":14.6},
//   {"x":99.9,"y":45.5}
// ]

点是顺时针还是逆时针顺序并不重要。

以下代码是我提出的,但在最坏的情况下,需要进行n^2+n点比较。是否有更有效的算法将所有这些加在一起?

function orderedPoints(segs){
  segs = segs.concat(); // make a mutable copy
  var seg = segs.pop(), pts = [seg.va], link = seg.vb;
  for (var ct=segs.length;ct--;){
    for (var i=segs.length;i--;){
      if (segs[i].va==link){
        seg = segs.splice(i,1)[0]; pts.push(seg.va); link = seg.vb;
        break;
      }else if (segs[i].vb==link){
        seg = segs.splice(i,1)[0]; pts.push(seg.vb); link = seg.va;
        break;
      }
    }
  }
  return pts;
}

3 个答案:

答案 0 :(得分:3)

如果你的多边形是凸的,你可以选择每个线段的中点,然后使用convex hull算法通过中间项找到凸多边形,之后,因为你知道什么是middles的排列,你也知道知道哪个中间属于哪个段,你可以在原始数组中找到一个排列。

如果你只是想找到一个凸包,直接使用凸包算法,它是O(n log n),它足够快,但你也可以在javascript here中找到一个Quickhull算法。 quickhull也在O(n logn),但平均而言,最坏的情况是O(n ^ 2),但由于常数因子较少,因此速度很快。


但是在通用算法的情况下:

将每个段的一端设置为First,将另一端设置为second(随机)。

按照第一个x对细分进行排序,然后将其放入数组First中,然后将其放在第一个排序段中,第一个排序段的第一个x位于第一个y之后,放入两个额外int到您的结构中以保存具有相同第一个x的项目的开始和结束位置。

然后再使用第二个x值对您的细分进行排序,....并创建数组second

以上操作均为O(n log n)。

现在选择数组First中的第一个细分,在数组xFirst中搜索其第二个second值,如果找到相似的值,请搜索相关子数组中的y值(您具有相同x项的开始和结束位置)。您知道此订单只有一个细分受众群(也不是当前细分受众群),因此找到下一个细分受众群需要O(log n),因为总共有n-1下一个细分受众群需要O(n logn)(同样预处理),它比O(n^2)快得多。

答案 1 :(得分:2)

应该可以在线性时间内将点转换为(双重,无序?)链表:

for (var i=0; i<segments.length; i++) {
    var a = segments[i].va,
        b = segments[i].vb;
    // nexts being the two adjacent points (in unknown order)
    if (a.nexts) a.nexts.push(b); else a.nexts = [b];
    if (b.nexts) b.nexts.push(a); else b.nexts = [a];
}

现在你可以迭代它来构建数组:

var prev = segments[0].va,
    start = segments[0].vb, // start somewhere, in some direction
    points = [],
    cur = start;
do {
    points.push(cur);
    var nexts = cur.nexts,
        next = nexts[0] == prev ? nexts[1] : nexts[0];
    delete cur.nexts; // un-modify the object
    prev = cur;
    cur = next;
} while (cur && cur != start)
return points;

如果您不想修改对象,EcmaScript6 Map(带对象键)会派上用场。作为一种变通方法,您可以使用点坐标的JSON序列化作为普通对象的键,但是您将被限制为不包含两次坐标的多边形。或者只使用库添加到顶点的唯一voronoiId属性来识别它们。

答案 2 :(得分:2)

对于凸多边形,您甚至不需要知道侧面分段。你只需要一堆顶点。订购顶点的过程非常简单。

  1. 将所有顶点平均在一起以获得多边形内的点。请注意,这甚至不需要是质心。它只需要是多边形内的一个点。称此为C。
  2. 对于每个顶点V [i],计算从V [i]到C的线段与从V [i]到V [i] +(1,0)的线段形成的角度。称之为[i]。
  3. 使用顶点作为卫星数据对顶点的角度进行排序。
  4. 有序顶点按多边形顺序排列。你可以删除一些冗余。 1以线性时间运行,2以线性时间运行,3次以n log n运行。