如何从范围列表中获取所有最大范围的最大不重叠范围

时间:2019-02-01 09:23:46

标签: python algorithm

我似乎无法找到一种方法来在标题中编写算法,而无需以某种方式来整理结果。

为了说明我想要的:

all_spans = [(0, 5), (2, 7), (5, 8), (6, 10), (9, 10), (11, 15)]
possible_sets = [
    {(0, 5), (5, 8), (9, 10), (11, 15)},
    {(2, 7), (9, 10), (11, 15)},
    {(0, 5), (6, 10), (11, 15)}
]
not_possible = [
    {(0, 5), (5, 8), (6, 10), (11, 15)},  # has overlaps
    {(5, 8), (9, 10), (11, 15)}           # not maximal w.r.t possible_sets[0]
]

我目前的实现方式或多或少是这样的:

def has_overlap(a, b):
    return a[1] > b[0] and b[1] > a[0]

def combine(spans, current, idx=0):
    for i in range(idx, len(spans)):
        overlaps = {e for e in current if has_overlap(e, spans[i])}
        if overlaps:
            yield from combine(spans, current-overlaps, i)
        else:
            current.add(spans[i])
    yield current

但是它会产生非最大跨度,我宁愿先不创建跨度。

>>> for s in combine(all_spans, set()):
...     print(sorted(s))
[(9, 10), (11, 15)]
[(6, 10), (11, 15)]
[(5, 8), (9, 10), (11, 15)]
[(9, 10), (11, 15)]
[(6, 10), (11, 15)]
[(2, 7), (9, 10), (11, 15)]
[(0, 5), (9, 10), (11, 15)]
[(0, 5), (6, 10), (11, 15)]
[(0, 5), (5, 8), (9, 10), (11, 15)]

是否有避免这种行为的其他方法?我在关键字“时间间隔重叠”和“活动计划”下发现了类似的问题,但是似乎没有一个问题涉及到此特定问题。

3 个答案:

答案 0 :(得分:2)

这取决于您不希望整理结果的意思。

将生成器与以下各项配合使用后,您可以滤除非最大结果:

all_results = [s for s in combine(all_spans, set())]

for first_result in list(all_results):
    for second_result in list(all_results):
        if first_result.issubset(second_result) and first_result != second_result:
            all_results.remove(first_result)
            break

如果不首先生成它们,可以在屈服之前进行检查以查看答案是否最大。像这样:

def combine(spans, current, idx=0):
    for i in range(idx, len(spans)):
        overlaps = {e for e in current if has_overlap(e, spans[i])}
        if overlaps:
            yield from combine(spans, current-overlaps, i)
        else:
            current.add(spans[i])
    # Check whether the current set is maximal.
    possible_additions = set(spans)
    for item_to_consider in set(possible_additions):
        if any([has_overlap(item_in_current, item_to_consider) for item_in_current in current]):
            possible_additions.remove(item_to_consider)
    if len(possible_additions) == 0:
        yield current

答案 1 :(得分:1)

这是一个简单的(?)曲线图的问题。制作一个有向图,其中每个跨度都是一个节点。假说,如果跨度B在跨度A完成之前没有开始,那么存在A边(从节点A到节点B)iff A [1] <= B [0]。你的图形看起来就像

Node    =>  Successors
(0, 5)  =>  (5, 8), (6, 10), (9, 10), (11, 15)
(2, 7)  =>  (9, 10), (11, 15)
(5, 8)  =>  (9, 10), (11, 15)
(6, 10) =>  (11, 15)
(9, 10) =>  (11, 15)

现在,问题减少到只需要查找图中的最长路径(包括领带)即可。

鉴于问题的线性,找到一个最大的解比较容易:在每个步骤中,选择终止时间最快的后继节点。步骤:

  1. 首先,所有节点都可用。与最快结束时间的一个是(0,5)。
  2. 具有最早结束的后继者(0,5)是(5,8)。
  3. 的后继者(5,8)...是(9,10)
  4. ...最后加入(11,15)

请注意,此多不需要的曲线图。仅仅是一个结构你愿意通过第一或第二子元件参考。

您已经知道,解决方案的长度为4。

你能走在这里形成的?

答案 2 :(得分:0)

假定范围是按下限排序的,我们想将当前范围附加到可以附加的最长路径上,或创建一个新路径(附加到空路径上)。如果需要,我们可以考虑使搜索最长前缀的效率更高。 (下面的代码只是以稍微优化的线性方法更新搜索。)

(我不确定如何使用yield功能,也许您可​​以使此代码更加优雅。)

# Assumes spans are sorted by lower bound
# and each tuple is a valid range
def f(spans):
  # Append the current span to the longest
  # paths it can be appended to.
  paths = [[spans.pop(0)]]
  for l,r in spans:
    to_extend = []
    longest = 0
    print "\nCandidate: %s" % ((l,r),)
    for path in paths:
      lp, rp = path[-1]
      print "Testing on %s" % ((lp,rp),)
      if lp <= l < rp:
        prefix = path[:-1]
        if len(prefix) >= longest:
          to_extend.append(prefix + [(l,r)])
          longest = len(prefix)
      # Otherwise, it's after so append it.
      else:
        print "Appending to path: %s" % path
        path.append((l, r))
        longest = len(path)
    for path in to_extend:
      print "Candidate extensions: %s" % to_extend
      if len(path) == longest + 1:
        print "Adding to total paths: %s" % path
        paths.append(path)

  print "\nResult: %s" % paths
  return paths

all_spans = [(0, 5), (2, 7), (5, 8), (6, 10), (9, 10), (11, 15)]

f(all_spans)

输出:

"""
Candidate: (2, 7)
Testing on (0, 5)
Candidate extensions: [[(2, 7)]]
Adding to total paths: [(2, 7)]

Candidate: (5, 8)
Testing on (0, 5)
Appending to path: [(0, 5)]
Testing on (2, 7)

Candidate: (6, 10)
Testing on (5, 8)
Testing on (2, 7)
Candidate extensions: [[(0, 5), (6, 10)]]
Adding to total paths: [(0, 5), (6, 10)]

Candidate: (9, 10)
Testing on (5, 8)
Appending to path: [(0, 5), (5, 8)]
Testing on (2, 7)
Appending to path: [(2, 7)]
Testing on (6, 10)

Candidate: (11, 15)
Testing on (9, 10)
Appending to path: [(0, 5), (5, 8), (9, 10)]
Testing on (9, 10)
Appending to path: [(2, 7), (9, 10)]
Testing on (6, 10)
Appending to path: [(0, 5), (6, 10)]

Result: [[(0, 5), (5, 8), (9, 10), (11, 15)],
         [(2, 7), (9, 10), (11, 15)],
         [(0, 5), (6, 10), (11, 15)]]
"""