【问题标题】:Optimized approach to finding which pairs of numbers another number falls between?查找另一个数字介于哪对数字之间的优化方法?
【发布时间】:2014-03-08 20:17:42
【问题描述】:

给定一行区域的列表:

regions = [(10,25), (18, 30), (45, 60), ...] # so on so forth, regions can be overlapping, of variable size, etc.

我想知道点 X 属于哪些区域:

x = 23
find_regions(regions, x) # ==> [(10, 25), (18, 30)]

我天真地知道(以及我当前的实现)我们可以只在 O(n) 中进行搜索,但是具有数千个区域(还有数千个查找点,真的是动机)的一个更具戏剧性的用例 em>) 证明研究比这更快的方法是合理的:

regions = [(start, end) for (start, end) in regions if start < x and x < end]

我会冒险猜测有人已经解决了这个问题......但我不确定如何最好地完成它。想法?

【问题讨论】:

  • 为什么find_regions(regions, x) 会返回[(10, 20), (22, 30)]
  • 忘记将该样本更新为原始定义 (18, 30)
  • 我还是不明白。在什么意义上23“属于”区域(10, 20)
  • 原来我把这两个都搞砸了
  • 您是否需要在输入介于最小值和最大值之间的可能区域列表中查找所有区域?这将在一定程度上增加计算负担——如果下限符合条件,没有算法可以在不检查其上限的情况下排除范围,反之亦然。我自己无法回答这个问题,只是说如果你有单调递增的下限,你至少可以从二分搜索算法中受益,以找到可能符合条件的范围。但是当多个范围覆盖输入时,任何算法都需要定义正确的行为。

标签: python algorithm search data-structures


【解决方案1】:

我对您的列表理解所做的唯一更改是将其设为generator,缩短与start &lt; x &lt; end 的比较,如果您只需要一个,可选择调用next()

>>> regions = [(10,25), (18, 30), (45, 60)]
>>> x = 23
>>> next((start, end) for (start, end) in regions if start < x < end)
(18, 30)

另请注意,您的比较 start &gt; x and x &lt; end 有一个倒退的 &gt;。应该是start &lt; x and x &lt; end。该修复包含在我的答案中


编辑:看到有关二分搜索的 cmets 和答案让我意识到我当然完全错误地认为缺乏改进的空间。也就是说,为了稍微改进比较和通过next() 短路,我仍然会保留这个答案。但与二分查找相比,我的改进是微不足道的。

我已经使您的搜索线性更快。二进制是对数的。

【讨论】:

    【解决方案2】:

    如果区域重叠,只需对区域进行排序并进行二分搜索。

    如果区域重叠,则为每个重叠区域计算重叠区域列表并将它们存储为列表。然后进行二分查找。

    例如:(1,10),(5,15) 转换为

    (1,4), (5,10), (11, 15)
      |       |        |
    (1,10) (1,10)  (5,15)
              |
           (5,15) 
    

    即链接 (5,10) 到它所属的区域。

    注意:这些只是线索,你需要做更多的工作。

    【讨论】:

    • 排序本身是O(NlogN),而他最初的想法是O(N)。但从长远来看,预排序方法会击败 O(N^M) 方法。
    【解决方案3】:

    我建议您将所有内容拆分为不重叠的基本区间,以便每个基本区间要么完全覆盖,要么完全超出任何给定区间。然后创建一个从基本区间到给定区间的映射。因为基本区间不重叠,所以使用二分搜索很容易找到匹配的区间。从那里您可以查找映射到它的实际间隔。 初始排序是 O(N log N),构建地图是 O(N),最终查找是 O(log N),因为是二分查找。基本区间数小于2*N。

    这是一个粗略的实现。不确定搜索点恰好到达结束间隔结束的情况。

    class IntervalFinder():
        elem_list = [] # the borders of the elementary interval
        elem_sets = [] # the actual intervals mapped to each elementary
        def __init__( self, intervals ):
            # sort the left ends
            a = sorted( intervals )
            # sort the right ends
            b = sorted( intervals, key=lambda x : x[1] )
            ia = 0 # index into a
            start = a[0][0] # the start of the elementary interval
    
            # the set of actual intervals covering the
            # current elementary
            current = set()
            for xb in b:
                while ia < len(a) and a[ia][0] < xb[1]:
                    stop = a[ia][0]
                    # an elementary interval ends here
                    # because a new interval starts
                    if stop > start:
                        self.elem_sets.append( set( current ) )
                        self.elem_list.append(start)
                        start = stop
                    current.add( a[ia] )
                    ia += 1
    
                if start < xb[1]:                    
                    self.elem_sets.append(set(current))
                    self.elem_list.append(start)
                    start = xb[1]
    
                current.remove( xb )
    
            self.elem_sets.append(set())
            self.elem_list.append(start)
    
    
        def find( self, a ):
            k = bisect.bisect( self.elem_list, a ) - 1
            if k<0:
                return set()
            # if its exactly on the border
            # it belongs to both the right and the left
            if a == self.elem_list[k]: 
                h = set(self.elem_sets[k])
                return h.union( self.elem_sets[k-1] )
            else:
                return self.elem_sets[k]
    
    intervals = [ ( 1, 10), (5, 15), (10, 20), (5, 30) ]
    
    ifind = IntervalFinder(intervals)
    for x in [0, 4,5,9,10,11, 20, 25, 30, 35]:
        print( x, ifind.find(x) )
    

    【讨论】:

      【解决方案4】:

      这正是interval trees 设计的工作。谷歌搜索Python interval tree 发现了一个名为Banyan 的现有库,它实现了它们,尽管我不能说它的可靠性,而且它似乎没有被积极开发。您也可以实现自己的区间树。

      从 N 个区间列表构建区间树的预处理时间为 O(Nlog(N)),并且与其他一些答案不同,它只需要 O(N) 空间,而不管区间有多少重叠。计算一个给定点有多少个区间重叠的时间是 O(M+log(N)),其中 M 是包含该点的区间数。

      榕树区间树演示,取自PyPI page

      >>> t = SortedSet([(1, 3), (2, 4), (-2, 9)], updator = OverlappingIntervalsUpdator)
      >>>
      >>> print(t.overlap_point(-5))
      []
      >>> print(t.overlap_point(5))
      [(-2, 9)]
      >>> print(t.overlap_point(3.5))
      [(-2, 9), (2, 4)]
      >>>
      >>> print(t.overlap((-10, 10)))
      [(-2, 9), (1, 3), (2, 4)]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-10-23
        • 2023-01-16
        • 1970-01-01
        • 1970-01-01
        • 2015-07-28
        • 2020-09-13
        • 1970-01-01
        • 2012-06-29
        相关资源
        最近更新 更多