我有一个200k行的数字范围列表,如start_position,停止位置。 除了不重叠的列表之外,该列表还包括各种重叠。
列表看起来像这样
我需要找到给定数字落入的范围。并且将重复它为10万个数字。 例如,如果18是上面列表的给定数字,那么函数应该返回 [10,30] [15,25]
我使用bisect以过于复杂的方式进行,任何人都可以更快地了解如何做到这一点。
由于
答案 0 :(得分:10)
这似乎是范围覆盖问题。由于您需要处理大量查询,因此需要尽快给出结果。有一个与此问题相关的算法,您可以查看Segment Tree。
我们的想法是首先根据给定的时间间隔构建一个Segment Tree,然后对于每个查询,您可以像log(N)
一样快,其中N
是间隔数。
要获得所有可能的k
区间,首先找到覆盖所有子区间log(n)
的父节点,然后遍历其子节点以检索所有k个区间。因此,检索给定数字的所有区间的时间复杂度受log(N) + O(k)
的限制,其中k << n
。
当所有区间包含给定数字时,此算法可能会恶化到O(N)
的速度。在这种情况下,由于输出的大小为N
,因此不存在任何更好的解决方案。因为算法的复杂性必须至少与输出大小相同或高于输出大小。
希望它有所帮助。
答案 1 :(得分:3)
解决此问题的最佳方法是构造一个Interval树。 (由sza给出的范围树是静态的。这意味着在构造它之后你不能添加/删除节点。如果你没问题,那么他的答案就足够了。)
您可以在此处了解间隔树:
Youtube:https://www.youtube.com/watch?v=q0QOYtSsTg4
维基:https://en.wikipedia.org/wiki/Interval_tree
区间树基本上是基于范围元组的左值的二叉树。 ([左,右])它的特殊之处在于树中的每个节点都有一个名为max的值。最大值保存在此节点下方的节点的最大正确值,包括节点本身。换句话说,当前节点将其最大值设置为当前节点为根的子树的最大右侧值。
要针对您的问题进行扩展,我们也可以添加最小值。其中min值将保存此节点为根的所有子树的最小左值。
从视频中编辑过的screencap :(对不起质量)
这意味着当我们查询你的数字时:
1) add current node to set of answers if number is inside range
2) go left if number is less than max value of left child.
3) go right if number is greater than min value of right child.
答案 2 :(得分:2)
import random as r
rng = range(99)
lefts = [r.choice(rng) for x in range(0, 200000)]
segments = [(x, r.choice(range(x+1, 100))) for x in lefts]
leftList = sorted(segments, key=lambda x:x[0])
rightList = sorted(segments, key=lambda x:x[1])
def returnSegments(item, segments, leftList, rightList):
for leftN in range(len(leftList)):
if leftList[leftN][0] > item:
break
for rightN in range(len(rightList)):
if rightList[rightN][1] > item:
break
return list(set(leftList[:leftN]) & set(rightList[rightN:]))
def testReturnSegments():
for item in range(0,100):
item = item % 100
returnSegments(item, segments, leftList, rightList)
此代码使用200k段和100个数字。它在9.3秒内在我的2.3 GHz i5 macbook上运行,意味着完整的200k x 100k运行,你需要2.5小时。可能还不够快,但可能有办法加速这种方法 - 我会考虑它。
答案 3 :(得分:2)
好的,这是一个树实现:
import itertools
class treeNode:
def __init__(self, segments):
self.value = None
self.overlapping = None
self.below = None
self.above = None
if len(segments) > 0:
self.value = sum(itertools.chain.from_iterable(segments))/(2*len(segments))
self.overlapping = set()
belowSegs = set()
aboveSegs = set()
for segment in segments:
if segment[0] <= self.value:
if segment[1] < self.value:
belowSegs.add(segment)
else:
self.overlapping.add(segment)
else:
aboveSegs.add(segment)
self.below = treeNode(belowSegs)
self.above = treeNode(aboveSegs)
else:
self.overlapping = None
def getOverlaps(self, item):
if self.overlapping is None:
return set()
if item < self.value:
return {x for x in self.overlapping if x[0]<=item and item<=x[1]} | self.below.getOverlaps(item)
elif item > self.value:
return {x for x in self.overlapping if x[0]<=item and item<=x[1]} | self.above.getOverlaps(item)
else:
return self.overlapping
<强>样本强>:
import random as r
maxInt = 100
numSegments = 200000
rng = range(maxInt-1)
lefts = [r.choice(rng) for x in range(0, numSegments)]
segments = [(x, r.choice(range(x+1, maxInt))) for x in lefts] # Construct a list of 200,000 random segments from integers between 0 and 100
tree = treeNode(segments) # Builds the tree structure based on a list of segments/ranges
def testReturnSegments():
for item in range(0,100000):
item = item % maxInt
overlaps = tree.getOverlaps(item)
import cProfile
cProfile.run('testReturnSegments()')
这使用200k段,找到10万个数字的重叠段,并在我的2.3 GHz Intel i5上运行94秒。这是cProfile输出:
<强>输出强>:
1060004 function calls (580004 primitive calls) in 95.700 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 95.700 95.700 <string>:1(<module>)
580000/100000 11.716 0.000 93.908 0.001 stackoverflow.py:27(getOverlaps)
239000 43.461 0.000 43.461 0.000 stackoverflow.py:31(<setcomp>)
241000 38.731 0.000 38.731 0.000 stackoverflow.py:33(<setcomp>)
1 1.788 1.788 95.700 95.700 stackoverflow.py:46(testReturnSegments)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.004 0.004 0.004 0.004 {range}
答案 4 :(得分:0)
def get_containing_intervals(x):
for start, end in intervals:
if start <= x <= end:
yield x
此处使用<=
基于假设间隔包含(封闭式)。
如果您打算多次调用此函数,您可能希望进行某种预处理,例如: @sza建议的是什么。
答案 5 :(得分:0)
怎么样,
按第一列O(n log n)
二进制搜索以查找超出范围O(log n)的索引
抛出超出范围的值
按第二列O(n log n)
二进制搜索以查找超出范围O(log n)的索引
抛出超出范围的值
您将获得范围
这应该是O(n log n)
您可以使用np.sort对行和列进行排序,而二进制搜索只能是几行代码。
如果您有大量查询,可以为后续调用保存第一个已排序的副本,但不保存第二个。根据查询的数量,进行线性搜索可能比排序然后搜索更好。