Python:优化区间之间的成对重叠

4 投票
1 回答
592 浏览
提问于 2025-04-18 03:44

我有很多区间,大约有五千到一万条。这些区间都有一个开始位置和一个结束位置,比如说(203, 405)。这些区间的坐标都存储在一个列表里。

我想找出每一对区间之间重叠部分的坐标和长度。可以通过以下方式来实现:

# a small list for clarity, with length normally around 5000s
cList = ((20, 54), (25, 48), (67, 133), (90,152), (140,211), (190,230)) 
for i, c1 in enumerate(cList[:-1]): # a linear pairwise combination
    for c2 in cList[i + 1:]:
        left =  max(c1[0], c2[0])
        right = min(c1[1], c2[1])
        overlap = right - left
        if overlap > 0:
            print "left: %s, right: %s, length: %s" % (left, right, overlap)

结果是:

left: 25, right: 48, length: 23
left: 90, right: 133, length: 43
left: 140, right: 152, length: 12
left: 190, right: 211, length: 21

可以看到,这个方法是有效的……不过这个过程可能会花费不少时间(大约20秒),所以我想知道怎么优化一下这个过程。我尝试在第二个循环中,当第二个区间的开始位置超过第一个区间的结束位置时,就停止第二个循环:

if c1[1] < c2[0]:
    break

这样做确实大大缩短了处理时间,但得到的重叠数量大约比之前少了三倍,因此结果肯定是不准确的。这是因为有些区间的长度远大于前面的区间。

我相信一定有什么数学技巧可以解决这个问题。

1 个回答

5

一般来说,如果你先把区间按照起始点排序,这类问题会变得简单很多。

首先,我们定义一个函数,这样会更清楚:

def overlap( r1, r2 ):
    left =  max(r1[0], r2[0])
    right = min(r1[1], r2[1])
    over = right - left
    return (left, right, over) if over>0 else None

你描述的算法可以写成:

for i, c1 in enumerate(cList[:-1]): 
    for c2 in cList[i + 1:]:
        o = overlap(c1,c2)
        if not o is None:
            print "left: %s, right: %s, length: %s" % o

如果你对元素进行排序,一旦找到一个不重叠的区间,就可以“短路”处理,因为你知道列表中后面的区间会更远:

l= sorted(cList)
for i, c1 in enumerate(l[:-1]): 
    for c2 in l[i + 1:]:
        o= overlap(c1,c2)
        if o is None:
            break
        print "left: %s, right: %s, length: %s" % o

当然,如果你的输入数据已经排好序(看起来是这样),你就可以跳过这一步。

需要注意的是,通常情况下,你可以用更清晰的 itertools.combinations 来代替双重 for 循环。这样也能保证相同的顺序。不过,遗憾的是,这种方法不适合优化后的算法,但你的算法可以写成:

from itertools import combinations
for c1,c2 in combinations(cList, 2):
    o= overlap(c1,c2)
    if not o is None:
        print "left: %s, right: %s, length: %s" % o

最后,如果你想对区间进行快速的通用操作,你可能还想考虑使用 区间树 数据结构。在 pypi上有一个Python实现

撰写回答