07_3.优先队列
连续表实现优先队列:
"""基于list实现优先队列""" class PrioQueueError(ValueError): pass class PrioQueue: """ 数据的存储位置按优先顺序排列 值较小的元素优先级更高 """ def __init__(self, elist=[]): self._elems = list(elist) # 默认值是可变对象,用list转换,做一个拷贝,避免共享 self._elems.sort(reverse=True) def enqueue(self, e): i = len(self._elems) - 1 while i >= 0: if self._elems[i] <= e: i -= 1 else: break # while结束,i为-1或第一个大于e的元素的下标 self._elems.insert(i + 1, e) def is_empty(self): return not self._elems def peek(self): if self.is_empty(): raise PrioQueueError('in top') return self._elems[-1] def dequeue(self): if self.is_empty(): raise PrioQueueError('in pop') return self._elems.pop() p = PrioQueue([1, 2, 3, 4]) print(p._elems) p.enqueue(5) print(p._elems)
采用线性表实现优先队列,无论是连续表还是链表,在插入元素与取出元素的操作中总有一种是具体线性复杂度的操作
优先队列的堆实现:
"""基于堆(小顶堆)实现优先队列 堆中每个结点的数据均小于或等于其子结点的数据 解决堆插入和删除的关键操作称为筛选,分向上筛选,向下筛选 去掉一个堆中最后的元素(最下层的最右结点),剩下的元素仍构成一个堆 - 插入元素: 在一个堆的最后加入一个元素,得到的结果还可以看作完全二叉树,但未必是堆,需要做一次向上的筛选 - 向上筛选: 不断用新加入的元素(e)与其父结点的数据比较,如果e较小就交换两个元素的位置,通过比较和交换,元素e 不断上移,一直到e的父结点的数据<=e时,或者e已经到达根结点时停止。 插入操作总结:把新加入元素放在(连续表里)已有元素之后,执行一次向上筛选操作。向上筛选操作中比较和交换的 次数不会超过二叉树中最长路径的长度,根据完全二叉树性质,加入元素操作可在O(logn)时间完成 - 弹出元素: 由于堆顶元素就是最优元素,应该弹出的元素就是它,剩下的团扇可以看作两个子堆,从原堆的最后取下一个元素, 其余的元素仍然是堆,把这个元素放到堆顶就得到了一棵完全二叉数,需要做向下筛选恢复为一个堆 - 向下筛选: 两个子堆A,B的顶元素与原堆取的最后一个元素e比较大小,最小者作为整个堆的顶 * 若e不是最小,最小的必为A或B的根,如果A的最小,将其移到堆顶,相当于删除了A的顶元素 * 如果某次比较中e最小,以它为顶的局部树已经成为堆,整个结构也成为堆 * 或e已经落到底,这是整个结构也成为堆 """ class PrioQueueError(ValueError): pass class PrioQueue: """使用一个list存储元素,表尾加入元素,以首端为堆顶""" def __init__(self, elist=[]): self._elems = list(elist) if elist: self.buildheap() # 转变为堆 def enqueue(self, e): self._elems.append(None) self.siftup(e, len(self._elems) - 1) # 向上筛选 def siftup(self, e, last): elems, i, j = self._elems, last, (last - 1) // 2 # (last - 1) // 2 :最后一个元素的根元素位置 # 小顶堆,找到正确插入位置,检查过程中逐个下移 while i > 0 and e < elems[j]: elems[i] = elems[j] i, j = j, (j - 1) // 2 elems[i] = e def is_empty(self): return not self._elems def peek(self): if self.is_empty(): raise PrioQueueError('in top') return self._elems[0] def dequeue(self): """弹出元素""" if self.is_empty(): raise PrioQueueError('in pop') elems = self._elems e0 = elems[0] # 堆顶元素 e = elems.pop() # 弹出最后元素 if len(elems) > 0: self.siftdown(e, 0, len(elems)) return e0 # 向下筛选 def siftdown(self, e, begin, end): """采用拿着新元素找位置""" elems, i, j = self._elems, begin, begin * 2 + 1 # begin * 2 + 1: 第一个小堆的堆顶 while j < end: if j + 1 < end and elems[j + 1] < elems[j]: # 找elems[j + 1]与elems[j]中较小的 j += 1 if e < elems[j]: # e在三者中最小,已找到了位置 break elems[i] = elems[j] # elems[j]在三者中最小,上移 i, j = j, 2 * j + 1 elems[i] = e def buildheap(self): end = len(self._elems) for i in range(end // 2, -1, -1): self.siftdown(self._elems[i], i, end) p = PrioQueue([1, 3, 6, 4, 5]) print(p._elems) print('========') print(p.dequeue()) print(p.dequeue()) print(p.dequeue()) print(p.dequeue()) print(p.dequeue()) # [1, 3, 6, 4, 5] # ======== # 1 # 3 # 4 # 5 # 6
基于堆实现优先队列,创建操作的时间复杂度是o(n),插入和弹出的复杂度是o(log n)插入操作的第一步是在表的最后加入一个元素,可能导致list对象替换元素存储区,因此可能出现O(n)的最坏情况