算法:合并重叠段

时间:2015-09-15 12:22:23

标签: java arrays algorithm list sorting

我有以下ADT(未排序):List<Segment>

//direction is from 0 to 2pi
class Segment {
    int start;
    int end;
}

他们代表这种情况: enter image description here

如何进行合并阶段(示例中为绿色箭头)?显然,我需要迭代列表,每个段与所有其他段进行比较,并且如果可能的话,为每个段进行简单的合并(这很容易)。但是在第二次迭代中,我需要以某种方式返回到列表的开头并重新开始...所以我很难找到这个算法如何收敛。

编辑:片段可以是圆形 - 从1.75pi到0.5pi等......

4 个答案:

答案 0 :(得分:7)

按照开始时间对细分进行排序。

创建一个堆栈,用于存储合并的区间。

将已排序数组的第一个元素添加到堆栈中,然后对于数组中的每个元素,将其与堆栈顶部的元素进行比较

如果开始时间大于堆栈顶部元素的结束时间,请将时间间隔添加到堆栈中。

如果开始时间小于堆栈顶部元素的结束时间,则更新堆栈顶部元素的结束时间以匹配新元素的结束时间。

处理整个数组时,生成的堆栈应包含合并的区间。

java上的实现:

/**
 * Definition for an interval.
 * public class Interval {
 *     int start;
 *     int end;
 *     Interval() { start = 0; end = 0; }
 *     Interval(int s, int e) { start = s; end = e; }
 * }
 */
public class Solution {
    public List<Interval> merge(List<Interval> intervals) {
        Deque<Interval> stack = new ArrayDeque<Interval>();

        Collections.sort(intervals, new Comparator<Interval>() {
                public int compare(Interval p1, Interval p2) {
                    return Integer.compare(p1.start, p2.start);
                }
            }
        );

        if (intervals.size() < 1) {
            return intervals;
        }
        stack.push(intervals.get(0));

        for (int j = 1; j < intervals.size(); j++) {
            Interval i = intervals.get(j);
            Interval top  = stack.peek();
            if (top.end < i. start) {
                stack.push(i);
            }
            else if (top.end < i.end) {
                top.end = i.end;
            }
        }
        return new ArrayList<Interval>(stack);

    }
}

答案 1 :(得分:6)

按起点对细分进行排序。

然后,对于每个分段,如果其起点位于前一分段的起点和终点之间且其终点大于前一分段的终点,则将前一分段的终点设置为该分段的终点并删除/忽略当前段。

如果当前片段完全包含在上一个片段中,则只需删除/忽略它。

由于排序,这是O(n log n),您无需将每个细分与所有其他细分进行比较。

小心如何删除或忽略。它应该是O(1)操作。例如,保留一个始终存储前一个未删除段的变量,也可以保留一些已删除段的标志,以便您知道最后要收集哪些段。对集合.remove()操作可以O(n),您希望避免这种情况。

答案 2 :(得分:5)

将所有端点放在一个数组中并为它们指定极性(+然后-)。然后对列表进行排序。

当您通过增加值遍历列表时,只需更新重叠段的计数器即可。

0+ 0.75- 0.5+ 1- 1.25+ 2-

然后,排序,

0+ 0.5+ 0.75- 1- 1.25+ 2-

给出计数(在0初始化)

1 2 1 0 1 0

因此区间界限(在转换0 to >0>0 to 0

0 1 1.25 2

这也可以完全就地无需额外标记

您可以就地分别对startend值进行排序(不要将Segments作为一个整体移动);这样,极性仍然是隐含的。

然后遍历列表作为两个排序列表的合并(使用两个独立索引),并维护重叠计数器。您可以就地覆盖边界,因为合并的结果没有更多的间隔。

开始
[0 0.75][0.5 1][1.25 2]

这两个列表都是偶然排序的。

0 0.5 1.25 (+)
0.75 1 2   (-)

继续进行合并,它将按顺序选择元素

+ + - - + -

并通过移动值

获得最终结果
[0 1][1.25 2][x x]

如果绑定边界,最好按顺序处理+-,以避免发出两个相等的边界。

答案 3 :(得分:2)

我会在其他答案中添加循环方法,即如果你有一些循环超过2pi的分段你应该怎么办。

有两种方法可以解决这个问题。一个是简单地将每个这样的段分成两个:一个从哪里到2pi,另一个从零到任何地方。在此之后,解决问题,就好像它不是圆形,然后如果你有一个段从零开始,一个段结束于2pi,那么只需合并它们。

第二种方法专门针对Yves Daoust的回答。你需要的只是知道有多少段覆盖零点(你可以很容易地计算出来);在此之后,你初始化&#34;计数&#34;不是零,而是有这个覆盖段的数量。