是否有任何有效的“顶部船体”算法?

时间:2019-03-06 16:06:39

标签: algorithm divide-and-conquer convex-hull

例如,当在x,y坐标中以(x-left,x-right,y)形式给出点时,(1,5,3),(2,4,5)返回(1,2 ,3),(2,4,5),(4,5,3)

3 个答案:

答案 0 :(得分:4)

一个简单的贪心算法可以很好地解决这个问题。 按y坐标(递减)对细分进行排序; ca ;;此列表RecyclerView。现在...

seg

请注意,间隔删除在某些情况下可能会发生:

top_hull = [empty set]
while seg is not empty
    head = seg.pop()    // pop off the highest segment.
    top_hull += head
    for each segment in seg
        remove the interval (head.xleft, head.y-left) from segment

取决于您的实现语言,区间代数可能会有出色的支持包。

答案 1 :(得分:2)

修剪答案有一个正确的主意,但我认为这对于解释如何检查间隔重叠是不公平的。实际上,该算法的该部分在二次时间O(n^2)中运行,因为它在某个点形成了所有n^2对,这是不必要的。我会做什么-

排队

首先,从细分列表中创建一个最大堆,并以y坐标为键。您可以提取和删除O(logn)时间中的最大值,因此这与排序一样复杂,只是弹出内置代码即可。

heap = max_heap(segement_list)
output = []
while heap is not empty
    segment = heap.pop() # max / highest

    # trim / split segment
    # append trimmed segment(s)

十字路口

现在,我们只需要修剪段。无需将其与其他所有线段配对并根据需要进行修剪,我们将使用另一个数据结构来允许我们快速查询潜在的交叉点。我们将每个添加的段存储在二进制搜索树中,并以较低的x坐标为其键。然后,我们可以遍历此树,以查找小于或等于我们要添加的段的最大段(通过较低的x坐标)。

为了使以下各段的技术细节不那么冗长,让我们仅避开两个关键比较的实现细节。假设段a的{​​{1}}值比lower_x小(因为在下面的段落中,我们将始终知道哪个较小)。

b

我们还需要三个转换,使用相同的# boolean- do `a` and `b` intersect function intersects(a, b) return a.upper_x >= b.lower_x # boolean- is `b` a subsegment of `a` function is_subsegment(a, b) return a.upper_x >= b.upper_x a定义-

b

返回查询BST片段的想法,即在查询段function merge(a, b) a.upper_x = b.upper_x function trim_left(a, b) a.upper_x = b.lower_x function trim_right(a, b) b.lower_x = a.upper_x 时获得的left_segment,看看它们是否相交。如果它们相交,请检查segment中的segment is_subsegment。如果是,请中止并将left_segment移至堆中的下一个段。否则,如果它们相交,则需要continue trim_right。无论是否有交叉点,我们都将处理任何右侧的交叉点。之后,我们可以将segment与经过修改的merge(实际上是segmentsubsegment重叠)一起left_segment

left_segment是唯一可以从左侧重叠的分段,因为我们将所有重叠的分段合并到BST中时将它们合并。但是,这不适用于右侧,因为我们的segment尚未从右侧进行修剪。我们将需要逐步处理修剪。

right_segment设为树中left_segment之后的下一段(按顺序遍历)。制作名为segment的{​​{1}}的副本。如果subsegmentsubsegment相交,将其修剪左,将right_segment插入输出数组,合并两个段,然后从BST中删除subsegment。否则,只需将right_segment插入数组。现在我们可以将subsegmentsubsegment合并(如果它们重叠)。如果没有,请在BST中插入left_segment,并为其分配变量subsegment

现在,我们重复此过程,直到从left_segmentsegment的{​​{1}}突围。然后,对堆中的下一个段重复 entire 过程。

分析

我们知道,形成最大堆并弹出最大is_subsegment次将导致left_segment时间复杂性。棘手的部分是弄清楚处理交叉口的时间复杂度。请注意,在处理并合并所有n后,对于我们处理的每个段,我们的BST总体上最多增加了一个。这是因为我们的所有O(nlogn)每次迭代都会合并在一起,因此最终形成了一个很大的细分。这意味着我们的BST不大于subsegment,因此查询和删除BST或将其插入需要花费subsegment的时间。

还要注意,对于插入BST的每个段,它将仅每个与另一个段相交一次-因为当这样做时,这两个(或更多)将合并为一个新的段。例外是,当一个段是其n的一个子段时,但是在这种情况下,我们中止而始终不发出任何新段,因此它的+0净大小始终会变化。使用此知识,再结合先前的观察结果,即每个段最终最多会为BST贡献一个新段,我们可以得出结论:最多会有O(logn)个交集,因此会有插入/删除。因此,left_segment时间来维持我们的BST。

鉴于我们其余的操作都是固定时间,因此在对十字路口进行修剪和修剪时,我们的整体时间复杂度为O(n),而不是O(nlogn)

答案 2 :(得分:2)

解决此问题的最简单有效(O(N log N))方法是使用“线扫”算法。

想象一下,在整个集合中从左向右扫过一条垂直线,并跟踪它穿过的最上段。每当最上面的航段发生变化时,这都是可能影响到顶部船身的重要事件。从这些更改的列表中计算顶部船体将很容易。

请注意,这些重要事件只能在输入段之一开始或结束的x位置发生。因此,我们只需要考虑在这x个位置会发生什么,而不是平稳地扫过该线。所以:

  1. 生成所有段开始和结束事件的列表。例如,对于段{1,2,5}会生成事件{1,开始5},{2,结束5}
  2. 创建最大堆以跟踪最上面的段。
  3. 按x位置排列所有事件,并按顺序处理它们。对于具有相同x位置的事件,请首先执行开始事件。这样可以确保您在每个细分受众群的结束事件之前对其进行处理。
  4. 对于列表中的每个x位置,以该x位置为单位处理所有事件。对于每个开始事件{x,开始y},将y添加到堆中。对于每个事件{x,结束y},从堆中删除y。
  5. 处理完某个x位置的事件后,堆中最大的元素是顶部船体的y位置,直到列表中的下一个x位置。