我对bisect和heapq有疑问。
首先,我将向您展示2个版本的代码,然后询问相关问题。
使用等分的版本:
while len(scoville) > 1:
a = scoville.pop(0)
#pops out smallest unit
if a >= K:
break
b = scoville.pop(0)
#pops out smallest unit
c = a + b * 2
bisect.insort(scoville, c)
使用heapq的版本
while len(scoville) > 1:
a = heapq.heappop(scoville)
#pops out smallest unit
if a >= K:
break
b = heapq.heappop(scoville)
#pops out smallest unit
c = a + b * 2
heapq.heappush(scoville, c)
这两种算法都使用2个pops和1个insert。
据我所知,在使用bisect的版本中,list的pop操作为O(1),而bisect类的插入操作为O(logn)。
在使用heapq的版本中,堆的弹出操作平均为O(1),堆的插入操作平均为O(logn)。
因此,两个代码应大致具有相同的时间效率。但是,使用bisect的版本在某些代码挑战站点上一直未能通过时间效率测试。
有人猜得很好吗?
* scoville是整数列表
答案 0 :(得分:6)
您的假设是错误的。 pop(0)
O(1)也不是bisect.insort
O(logn)。
问题在于,在两种情况下,您弹出或插入的元素之后的所有元素都必须向左或可能向左移动一个位置,这两个操作都为O(n)。
摘自bisect.insort
文档:
bisect.insort_left(a, x, lo=0, hi=len(a))
按排序顺序插入x。假设a已经排序,则等效于a.insert(bisect.bisect_left(a,x,lo,hi),x)。 请记住,O(log n)搜索主要由缓慢的O(n)插入步骤决定。
您可以通过创建一个很长的列表(例如说l = list(range(10**8))
,然后进行l.pop(0)
或l.pop()
和bisect.insort(l, 0)
或bisect.insort(l, 10**9)
来进行测试。最后弹出和插入的操作应该是瞬时的,而其他操作则有短暂但明显的延迟。
如果您交替弹出并插入,以便在数千次运行中列表的长度保持不变,也可以使用%timeit
在较短的列表上对其进行重复测试:
>>> l = list(range(10**6))
>>> %timeit l.pop(); bisect.insort(l, 10**6)
100000 loops, best of 3: 2.21 us per loop
>>> %timeit l.pop(0); bisect.insort(l, 0)
100 loops, best of 3: 14.2 ms per loop
因此,使用bisect
的版本为O(n),使用heapq
的版本为O(logn)。