【问题标题】:Grouping objects based on overlapping time interval基于重叠时间间隔对对象进行分组
【发布时间】:2015-08-30 20:49:32
【问题描述】:

我有课,

class A{
    ...
    private Interval interval;
    ...
}

//maintaining epoch time
class Interval{
    private long left;
    private long right;

    public Interval(long left, long right){
        this.left = left;
        this.right = right;
    }
//getters and setters
}

我有一个对象列表List<A>,我想对具有重叠(传递重叠,即a = b, b = c then a = c)间隔的对象进行分组。假设我有 4 个对象,

A1 - Interval - 09:00 to 10:00
A2 - Interval - 13:00 to 14:00
A3 - Interval - 10:10 to 12:00
A4 - Interval - 09:30 to 10:30

程序的结果应该给我2个列表,

List1 - A1,A3,A4
List2 - A2

关于解决此问题和/或伪代码的任何建议?

【问题讨论】:

  • 您可能想查看interval trees
  • 您一次要处理多少个间隔?我可以建议一个简单的解决方案,我认为间隔数应该是 O(n^2),如果你不是在一个组中处理很多它们,这不会太糟糕。
  • 将处理少于 1000 个对象。
  • 如果可以的话,在添加数据时按组维护数据,这将解决必须处理列表中每个元素的问题
  • @MadProgrammer:这实际上提出了一个很好的观点。我认为我正在研究的解决方案实际上是 n*log(n) in # of interval,但它假设您从间隔数开始,然后将它们全部合并到组中。它不是“在线”的,因为它不会在不重新处理的情况下自然地处理新添加的间隔。尽管它可以很容易地适应在线,因为它依赖于按起点对间隔进行排序。您可以使用二叉树(或类似的)进行在线排序,然后在需要时在 O(n) 中处理合并。

标签: java list grouping


【解决方案1】:

很抱歉冗长,但我相信这个问题值得 :-) 。这个解在区间数上是 n*log(n)。这种复杂性发生的位置在区间的排序范围内。请注意,此解决方案注意包括一个区间完全包含在另一个区间内的情况。结果组的子区间是反向排序的,但如果您愿意,可以通过在末尾反转列表来轻松解决此问题。我不建议通过在开头而不是结尾插入连接间隔来修复它,因为如果您使用基于 ArrayList 的实现,这会将复杂性增加到 O(n^2)。我想您可能可以使用 LinkedList 并在开头插入,但这可能会在其他地方对您造成伤害,具体取决于您在形成这些组后如何处理它们。

public static long max(long a, long b){
    return (a>b ? a : b);
}

public static long min(long a, long b){
    return (a<b ? a : b);
}

/**
 * A meta interval is composed of sub intervals. Its left endpoint is
 * the leftmost point of its subintervals and its right endpoint is 
 * the rightmost point of its subintervals. 
 */
static class MetaInterval extends Interval{

    /**
     * meta intervals are composed of lists of subintervals.
     */
    private List<Interval> subintervals;

    public MetaInterval(Interval initialSubInterval){
        super(initialSubInterval.getLeft(),initialSubInterval.getRight(),null);
        this.subintervals = new ArrayList<Interval>();
        this.subintervals.add(initialSubInterval);
    }

    public void join(MetaInterval other){
        verifyOverlap(other);

        this.setLeft(min(this.getLeft(),other.getLeft()));
        this.setRight(max(this.getRight(),other.getRight()));
        this.subintervals.addAll(other.getSubintervals());

        other.invalidate();
    }

    public void setSubintervals(List<Interval> subintervals){
        this.subintervals = subintervals;
    }

    public List<Interval> getSubintervals(){
        return this.subintervals;
    }

    private void invalidate(){
        this.setSubintervals(null);
        this.setLeft(0);
        this.setRight(0);
    }

    public boolean isInvalid(){
        return this.getSubintervals()==null;
    }


}

//maintaining epoch time
static class Interval implements Comparable<Interval>{
    private long left;
    private long right;

    private String intervalId;

    public Interval(long left, long right, String intervalId){
        this.left = left;
        this.right = right;
        this.intervalId = intervalId;
    }

    public boolean pointIsIn(long point){
        return (point>=this.getLeft() && point<=this.getRight());
    }

    public long getLeft(){
        return left;
    }

    public long getRight(){
        return right;
    }

    public void setLeft(long left){
        this.left = left;
    }

    public void setRight(long right){
        this.right = right;
    }

    public String getIntervalId(){
        return intervalId;
    }

    public boolean overlaps(Interval other){
        return pointIsIn(other.getLeft()) || pointIsIn(other.getRight()) || other.pointIsIn(getLeft()) || other.pointIsIn(getRight());
    }

    public void verifyOverlap(Interval other){
        if(!overlaps(other)){
            throw new IllegalStateException("Other interval does not overlap");
        }
    }

    /**
     * Sort by leftmost part of interval.
     */
    @Override
    public int compareTo(Interval o) {
        return Long.compare(this.getLeft(), o.getLeft());
    }

    public String toString(){
        return intervalId+":["+getLeft()+","+getRight()+"]";
    }
}

/**
 * Given a list of intervals, returns a list of interval groups where
 * the intervals in the groups all overlap. So if A overlaps with B and
 * B with C, then A,B,and C will be returned in a group. Supposing intervals
 * D  and E share nothing with A, B, or C, but do share with each other, D and E
 * will be returned as a separate group from A,B,C.
 * @param baseIntervals
 * @return
 */
public static List<List<Interval>> getOverlappingGroups(List<Interval> baseIntervals){
    List<MetaInterval> baseMetaIntervals = toMetaIntervals(baseIntervals);

    List<MetaInterval> mergedMetaIntervals = getMergedMetaIntervals(baseMetaIntervals);

    List<List<Interval>> intervalGroups = metaIntervalsToGroups(mergedMetaIntervals);
    return intervalGroups;
}



private static List<MetaInterval> getMergedMetaIntervals(
        List<MetaInterval> metaIntervals) {
    if(metaIntervals.isEmpty()){
        return metaIntervals;
    }
    //order the meta intervals by their starting point.
    Collections.sort(metaIntervals);

    //go through and merge the overlapping meta intervals.
    //This relies on the logic that if interval i overlaps with
    //an interval that started before it, then once all the intervals
    //before i have been merged, interval i will have a starting point
    //consecutive to the starting point of the the preceeding interval.
    for(int i=0; i< metaIntervals.size()-1; i++){
        MetaInterval thisInterval = metaIntervals.get(i);
        MetaInterval nextInterval = metaIntervals.get(i+1);

        if(thisInterval.overlaps(nextInterval)){
            nextInterval.join(thisInterval);
        }
    }

    List<MetaInterval> resultIntervals = new ArrayList<MetaInterval>();

    //All intervals from the original list either:
    //(a) didn't overlap with any others
    //(b) overlapped with others and were chosen to represent the merged group or
    //(c) overlapped with others, were represented in the group in another meta 
    // interval, and then marked as invalid.
    //Go through and only add the valid intervals to be returned.

    for(MetaInterval i : metaIntervals){
        if(!i.isInvalid()){
            resultIntervals.add(i);
        }
    }
    return resultIntervals;
}

/**
 * Convert a list of meta intervals into groups of intervals.
 * @param mergedMetaIntervals
 * @return
 */
private static List<List<Interval>> metaIntervalsToGroups(
        List<MetaInterval> mergedMetaIntervals) {
    List<List<Interval>> groups = new ArrayList<>(mergedMetaIntervals.size());
    for(MetaInterval metaInterval : mergedMetaIntervals){
        groups.add(metaInterval.getSubintervals());
    }
    return groups;
}

private static List<MetaInterval> toMetaIntervals(
        List<Interval> baseIntervals) {
    ArrayList<MetaInterval> metaIntervals = new ArrayList<MetaInterval>(baseIntervals.size());
    for(Interval i : baseIntervals){
        metaIntervals.add(new MetaInterval(i));
    }

    return metaIntervals;
}

public static void main(String[] args){
    Interval a = new Interval(20, 30, "A");
    Interval b = new Interval(21,22,"B");
    Interval c = new Interval(500,503,"C");
    Interval d = new Interval(28,38, "D");
    Interval e = new Interval(490,502,"E");



    //note: A,B, and D are an overlapping group, and
    //D and E are another.
    List<List<Interval>> intervalGroups = getOverlappingGroups(Arrays.asList(a,b,c,d,e));

    for(List<Interval> group : intervalGroups){
        System.out.println(group.toString());
    }
}

输出:

[D:[28,38], B:[21,22], A:[20,30]]
[C:[500,503], E:[490,502]]

【讨论】:

    【解决方案2】:

    定义Interval 类(我没有制作getter/setter——我就是那样懒惰):

    class Interval {
        public long left;
        public long right;
        public String name;
        public Interval(String name, long left, long right) {
            this.name = name;
            this.left = left;
            this.right = right;
        }
        public boolean overlaps(Interval other) {
            return (this.left >= other.left && this.left <= other.right)
                    || (this.right <= other.right && this.right >= other.left)
                    || (other.left >= this.left && other.left <= this.right)
                    || (other.right <= this.right && other.right >= this.left);
        }
    }
    

    现在我们可以运行这个程序了:

    public class Main {
        public static void main(String[] args) {
            ArrayList<Interval> all = new ArrayList<Interval>();
            all.add(new Interval("A",0,10));
            all.add(new Interval("B",30,40));
            all.add(new Interval("C",5,15));
            all.add(new Interval("D",15,30));
    
            // We run our algorithm...
            ArrayList<ArrayList<Interval>> result = new ArrayList<ArrayList<Interval>>();
            for (Interval interval : all) {
                if (result.isEmpty()) {
                    result.add(new ArrayList<Interval>());
                    result.get(0).add(interval);
                }else{
                    boolean addedSomewhere = false;
                    for (ArrayList<Interval> group : result) {
                        for (Interval other : group) {
                            if (other.overlaps(interval)) {
                                group.add(interval);
                                addedSomewhere = true;
                            }
                            if (addedSomewhere) break;
                        }
                        if (addedSomewhere) break;
                    }
                    if (!addedSomewhere) {
                        result.add(new ArrayList<Interval>());
                        result.get(result.size() - 1).add(interval);
                    }
                }
            }
    
            // Print the result...
            for (ArrayList<Interval> group : result) {
                System.out.print("{");
                for (Interval interval : group) {
                    System.out.print("[" + interval.name + "," + interval.left + "," + interval.right + "]");
                }
                System.out.print("}\n");
            }
        }
    }
    

    哪个输出这个:

    {[A,0,10][C,5,15][D,15,30]} 
    {[B,30,40]}
    

    【讨论】:

    • 你的重叠方法没有考虑this区间完全包围另一个的情况。
    • 这看起来 O(n^2),也许 O(n^3)。如果所有内容都重叠或没有重叠,则为 n^2。没有考虑过中间情况。
    • K,再想一想,它不会比 n^2 更糟。您仍然没有解决重叠问题,但没有考虑完全包含的间隔。在这些输入上尝试您的算法:Interval a = new Interval("A",20, 30); Interval b = new Interval("B",21,22); Interval c = new Interval("C",500,503); Interval d = new Interval("D",28,38); Interval e = new Interval("E",490,502);
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-30
    • 2021-04-29
    • 2021-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多