【问题标题】:Merge two lists of intervals with priority for one list将两个间隔列表合并为一个列表的优先级
【发布时间】:2017-10-24 15:23:36
【问题描述】:

我目前遇到了一个算法问题,我想优化复杂性。

我有两个区间列表 S = [[s1, s2], [s3, s4], ..., [sn-1, sn]]W = [[w1, w2], [w3, w4], ..., [wm-1, wm]] 我想按照序数顺序合并它们,S 的区间优先于 W 的区间。 (S为强,W为弱) 例如,该优先级意味着:

  • S = [[5,8]]W = [[1, 5], [7, 10]] 将给出:res = [[1, 4, W], [5, 8, S], [9, 10, W]]。这里从 W 开始的区间被优先裁剪为 S 的区间
  • S = [[5, 8]]W = [[2, 10]] 将给出:res = [[2, 4, W], [5, 8, S], [9, 10, W]]。这里 W 的区间被分成两部分,因为 S 具有优先级。

在合并这些列表时,我需要通过在每个间隔旁边写第三个元素来跟踪这些间隔的强弱特性,我们可以称之为符号。这就是为什么结果类似于:[[1, 4, W], [5, 8, S], [9, 10, W]]。 最后,由于所有区间的并集并没有覆盖一定范围内的所有整数,所以我们有第三个符号,假设 B 代表空白,填补缺失的区间:[[1, 2, W], [5, 8, S], [9, 10, W], [16, 20, S]] 将被填充为:[1, 2, W], [3, 4, B], [5, 8, S], [9, 10, W], [11, 15, B], [16, 20, S]]

我的第一次尝试非常天真和懒惰(因为我首先想让它工作): 如果这两个区间列表覆盖的最大整数是 M,那么我创建了一个大小为 M 的列表,其中填充了 B 符号:res = [B]*M = [B, B, B ..., B] 然后我首先从 W 开始一个接一个地取区间,并从这个区间的 index 的 res 中重写元素,以将其符号更改为 W。接下来,我对 S 的区间做同样的事情,并尊重优先级,因为我用符号 S 覆盖在最后一步。 它给出了类似的东西:

  1. [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B]
  2. [B, B, B, W, W, W, W, B, W, W, W, W, B, W, W, B, B]
  3. [B, B, S, S, W, W, W, B, S, S, W, W, B, S, W, B, B]

最后,我最后一次通过大列表来分解和重​​新创建间隔及其对应的符号。上一个示例给出: [[1, 2, B], [3, 4, S], [5, 7, W], [8, 8, B], [9, 10, S], [11, 12, W], [13, 13, B], [14, 14, S], [15, 15, W], [16, 17, B]]

不幸但可以预见的是,该算法在实践中不可用:M 在我的应用程序中约为 1000000,如果我不是,该算法为 O(n2)搞错了。

所以,我想要一些建议和指导来解决这个算法复杂性问题。我确信这个问题看起来像一个众所周知的算法问题,但我不知道该去哪里。

我目前改进的一些想法可用于优化算法,但实现起来相当复杂,所以我认为有更好的想法。但他们在这里:

  • 执行相同类型的覆盖过程以尊重优先级:在列表 W 中,在必要时插入 S 的间隔并覆盖以尊重优先级。然后填写此列表以插入带有 B 符号的缺失区间。但是由于大量的案例,我们会大量使用 if 来比较区间。
  • 在逐步浏览 S 和 W 的同时构造一个新列表。在这个想法中,我们将一个列表一个光标从一个间隔移动到另一个间隔,直到两个列表之一的末尾。我们再次使用了很多 if 并且我们在新列表中插入关于优先级的间隔。但它会在大量案例中引发同样复杂的问题。

我希望我说清楚了,否则我可以用其他方式解释。 请用经验和智慧教我:)

谢谢

编辑:这是我的“天真”算法代码:

def f(W, S, size):

  #We first write one symbol per sample
  int_result = ['B'] * size
  for interval in W:
      for i in range(interval[0], interval[1]+1):
          int_result[i] = 'W'
  for interval in S:
      for i in range(interval[0], interval[1]+1):
          int_result[i] = 'S'

  #we then factorize: we store one symbol for an interval of the same    symbol.    
  symbols_intervals = []
  sym = int_result[0]
  start = 0
  for j in range(len(int_result)):
      if int_result[j] != sym:
          symbols_intervals.append([start, j-1, sym])
          sym = all_symbols[j]
          start = j
      if j == len(int_result)-1:
          symbols_intervals.append([start, j-1, sym])

  return symbols_intervals

【问题讨论】:

  • 您的算法是 O(M),而不是 O(M^2)。它应该适用于 M = 1,000,000。你试过了吗?你能告诉我们你的代码吗?
  • O(n^2) 怎么样?你真的在使用大小为 M 的 list 吗?如果是这样,您可以将其替换为 array,这将为您提供更好的时间复杂度:O(M+n+m)。但是你实际上可以用你最后建议的两种方法中的任何一种来做 O(n+m) - 如果你的 M 很大,那就更好了。这确实需要许多 if 语句,但它仍然应该是可管理的!
  • 我的错,当你纠正亚历克斯时,这是 O(M)。它适用于 M = 1,000,000,但它使算法非常慢。在编辑的第一篇文章中查看我的代码;)

标签: python algorithm


【解决方案1】:

以下解决方案具有 O(n + m) 复杂度,其中 n 和 m 是 S 和 W 列表的长度。它假设 S 和 W 是内部排序的。

def combine(S, W):
    s, w = 0, 0 # indices of S and W
    common = []
    while s < len(S) or w < len(W):
        # only weak intervals remain, so append them to common
        if s == len(S):
            common.append((W[w][0], W[w][1], 'W'))
            w += 1
        # only strong intervals remain, so append them to common
        elif w == len(W):
            common.append((S[s][0], S[s][1], 'S'))
            s += 1
        # assume that the strong interval starts first
        elif S[s][0] <= W[w][0]:
            W[w][0] = max(W[w][0], S[s][1]+1)
            if W[w][0] > W[w][1]: # drop the weak interval
                w += 1
            common.append((S[s][0], S[s][1], 'S'))
            s += 1
        # assume that the weak interval starts first
        elif S[s][0] > W[w][0]:
            # end point of weak interval before the start of the strong
            if W[w][1] < S[s][0]:
                common.append(W[w][0], W[w][1], 'W')
                w += 1
            # end point of the weak interval between a strong interval
            elif S[s][0] <= W[w][1] <= S[s][1]:
                W[w][1] = S[s][0] - 1
                common.append((W[w][0], W[w][1], 'W'))
                w += 1
            # end point of the weak interval after the end point of the strong
            elif W[w][1] > S[s][1]:
                common.append((W[w][0], S[s][0]-1, 'W'))
                W[w][0] = S[s][1] + 1
    return common


print combine(S=[[5,8]], W=[[1, 5],[7, 10]])
print combine(S=[[5,8]], W=[[2,10]])

【讨论】:

    【解决方案2】:

    你的幼稚方法听起来很合理;我认为它的时间复杂度是 O(NM),其中 N 是您尝试解决的间隔数,而 M是您试图解决它们的范围。您可能遇到的困难是您也有 O(M) 的空间复杂度,这可能会占用相当多的内存。

    这里有一种不建立“主列表”的合并方法,可能会更快;因为它将区间视为对象,所以复杂性不再与 M 相关。

    我将一个区间(或区间列表)表示为一组元组(a,b,p),每个元组表示从ab的时间点,包括整数优先级pW 可以是1S 可以是2)。在每个区间中,必须是a b。优先级更高。

    我们需要一个谓词来定义两个区间之间的重叠:

    def has_overlap(i1, i2):
        '''Returns True if the intervals overlap each other.'''
        (a1, b1, p1) = i1
        (a2, b2, p2) = i2
        A = (a1 - a2)
        B = (b2 - a1)
        C = (b2 - b1)
        D = (b1 - a2)
        return max(A * B, D * C, -A * D, B * -C) >= 0
    

    当我们发现重叠时,我们需要解决它们。这种方法会照顾到这一点,尊重优先级:

    def join_intervals(i1, i2):
        '''
        Joins two intervals, fusing them if they are of the same priority,
        and trimming the lower priority one if not.
    
        Invariant: the interval(s) returned by this function will not
        overlap each other.
    
        >>> join_intervals((1,5,2), (4,8,2))
        {(1, 8, 2)}
        >>> join_intervals((1,5,2), (4,8,1))
        {(1, 5, 2), (6, 8, 1)}
        >>> join_intervals((1,3,2), (4,8,2))
        {(1, 3, 2), (4, 8, 2)}
        '''
        if has_overlap(i1, i2):
            (a1, b1, p1) = i1
            (a2, b2, p2) = i2
            if p1 == p2:
                # UNION
                return set([(min(a1, a2), max(b1, b2), p1)])
            # DIFFERENCE
            if p2 < p1:
                (a1, b1, p1) = i2
                (a2, b2, p2) = i1
            retval = set([(a2, b2, p2)])
            if a1 < a2 - 1:
                retval.add((a1, a2 - 1, p1))
            if b1 > b2 + 1:
                retval.add((b2 + 1, b1, p1))
            return retval
        else:
            return set([i1, i2])
    

    最后,merge_intervals 采用一个可迭代的区间并将它们连接在一起,直到不再有重叠:

    import itertools
    
    def merge_intervals(intervals):
        '''Resolve overlaps in an iterable of interval tuples.'''
        # invariant: retval contains no mutually overlapping intervals
        retval = set()
        for i in intervals:
            # filter out the set of intervals in retval that overlap the
            # new interval to add O(N)
            overlaps = set([i2 for i2 in retval if has_overlap(i, i2)])
            retval -= overlaps
            overlaps.add(i)
            # members of overlaps can potentially all overlap each other;
            # loop until all overlaps are resolved O(N^3)
            while True:
                # find elements of overlaps which overlap each other O(N^2)
                found = False
                for i1, i2 in itertools.combinations(overlaps, 2):
                    if has_overlap(i1, i2):
                        found = True
                        break
                if not found:
                    break
                overlaps.remove(i1)
                overlaps.remove(i2)
                overlaps.update(join_intervals(i1, i2))
            retval.update(overlaps)
        return retval
    

    我认为这具有 O(N^4) 的最坏情况时间复杂度,尽管平均情况应该很快。在任何情况下,您都可能希望根据您更简单的方法对该解决方案进行计时,看看哪种方法更适合您的问题。

    据我所知,我的 merge_intervals 适用于您的示例:

    # example 1
    assert (merge_intervals({(5, 8, 2), (1, 5, 1), (7, 10, 1)}) ==
            {(1, 4, 1), (5, 8, 2), (9, 10, 1)})
    
    # example 2
    assert (merge_intervals({(5, 8, 2), (2, 10, 1)}) ==
            {(2, 4, 1), (5, 8, 2), (9, 10, 1)})
    

    要覆盖空白 (B) 区间的情况,只需添加另一个区间元组,该元组以优先级覆盖整个范围 0:(1, M, 0):

    # example 3 (with B)
    assert (merge_intervals([(1, 2, 1), (5, 8, 2), (9, 10, 1),
                             (16, 20, 2), (1, 20, 0)]) ==
            {(1, 2, 1), (3, 4, 0), (5, 8, 2),
             (9, 10, 1), (11, 15, 0), (16, 20, 2)})
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-08
      • 2014-01-27
      • 1970-01-01
      • 2015-04-01
      • 1970-01-01
      • 2017-08-08
      相关资源
      最近更新 更多