优先队列
首先了解什么是优先队列
作为缓存结构,优先队列与栈和队列类似,可以将数据元素保存其中,可以访问和弹出。优先队列的特点是存入其中的内向数据都另外附有一个数值,表示这个项的优先程度,称其为优先级。每次访问的都会是优先级最高的元素。如果不止一个元素优先级最高,优先队列将推出他们中的一个,具体哪个将由每部实现确定
如果优先队列要同时保持FIFO的性质,那只能做出较低的效率,如果只要求每次保证访问的是优先元素最高的,不要求最早进入优先队列的元素,那么就存在效率更高的实现
优先队列的世界应用:
各项工作的计划开始时间
一个大项目中各种工作人物的紧急程度
银行客户的诚信评估
基于线性表的事项:
考虑:1、在存入数据时,保证表中元素始终按优先书序排列,任何时候都可以直接取到当时在表里最优先的元素。采用有组织的元素存放方式,存入元素的操作比较麻烦,效率可能较低,但访问和弹出是比较方便
2、存入数据是采用最简单的方式,需要时通过检索找到最优先的元素,这种方式,存入的效率高,单取用访问比较麻烦。如果需要多次访问统一个元素单不弹出,就应该采用其他技术,避免重复检索
基于list实现优先队列:这里以较小的作为较优先
class PriQueue: def __init__(self, elist=[]): self._elems = elist self._elems.sort(reverse=True) def insert(self, e): lenth = len(self._elems) - 1 while lenth 》= 0: if e < self._elems[lenth]: lenth = lenth -1 else: break self._elems.index(lenth + 1, e) def is_empty(self): return not self._elems def dequeue(self): if self._elems is None: return ValuerError return self._elems.pop()
对连续表实现的分析:
在以上各项操作中插入元素的时间复杂度是O(n),其他的都是O(1)。注意:急事插入式标点存储区满,需要换一块存储,操作效率的复杂度量级也没有改变,任然是O(n);如果使用的是第二种方案则,插入队列是时间复杂度是O(1),其余的操作是O(n)
实际上也可以用链表来实现优先队列,但是复杂情况和连续表类似。显然采用线性表技术实现的优先队列,无论用哪种方式,在插入元素与去除元素率的操作中中总有一种是具有线性复杂度的操作。这个有点蛋疼。。。。
面对这种情况我们应该怎么解决呢????????首先我们先来了解树形结构和堆
树形结构和堆
首先分析效率低的原因。其根源就是插入或者弹出元素时需要沿着表的顺序去检索位置,表的长度是n,检索的时间必然是O(n)。如果不改变数据的线性书序存储方式,就不可能突破O(n)的复杂度限制。
堆及其性质
采用树形结构实现优先队列的一种有效技术称为堆。从结构上看,堆就是结点里存储数据的完全二叉树。但对堆中数据的存储要满足一种特殊的堆序:任一个结点里所存的数据先于或等于其子结点里的数据
堆得定义理解:
- 在一个对堆中从树根到人和一个叶结点的路径上,根结点里所存的数据按规定的优先关系递减
- 堆中最优先的元素必定位于二叉树的根节点里(堆顶),O(1)时间就能得到
- 位于树中不同路径上的元素,这里不关心其顺序关系
堆和完全二叉树的几个重要的性质
- a、在一个堆得最后加上一个元素,整个结构还是可以看做一棵完全二叉树,但它未必是堆
- b、一个堆去电堆顶,其余元素形成两个“子堆“,完全二叉树的子结点/夫结点下边计算规则任然适用,堆序在路径上任然成立
- c、给由b得到的表加入一个根元素,得到的结点序列可看做完全二叉树,但未必是堆
- d、去电一个对重最后的元素,剩下的元素仍然构成一个堆
堆与优先队列
用堆作为优先队列还要考虑插入元素和弹出元素操作的实现,插入和弹出元素的关键操作称为筛选,又分为向上筛选好向下筛选
插入元素和向上筛选
根据性质a,此时的堆未必是二叉树,只需要做一次向上筛选(将新加入的元素不断的与其夫结点进行比较),这样就得到基于堆得优先队列的插入炒作
- 把新加入元素放在(连续表里)已有元素之后,执行一次向上筛选操作
- 向上筛选操作中比较和交换的此时不会超过二叉树中最长路径的长度。根据完全二叉树的性质,加入元素操作可以在O(log n)时间完成
弹出元素和向下筛选
根据性质b,将最后元素放入堆顶,向下做一次对比,完成弹出元素的操作
优先队列弹出的操作实现:
- 弹出当时的堆顶
- 从堆最后取一个元素作为完全二叉树的根
- 执行一次向下筛选
代码实现:
class PriQueue: def __init__(self, elist=[]): self._elems = list(elist) if elist: self.bulidheap() def is_empty(self): #判断空 return not self._elems def peek(self): # 查看元素 if self.is_empty(): raise ValueError return self._elems def enqueue(self, e): # 压入新元素 self._elems.append(None) self.siftup(e, len(self._elems) - 1) def siftup(self,e, last): elem, i, j = self._elems, last, (last - 1) // 2 while i > 0 and e < elem[j]: elem[i] = elem[j] i = j j = j // 2 elem[i] = e def dequeue(self): # 弹出元素 if self.is_empty(): return ValueError 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 while j < end: if j + 1 < end and elems[j + 1] < elems[j]: j += 1 if e < elems[j]: break elems[i] = elems[j] i, j = j, 2 * j + 1 elems[i] = e def bulidheap(self): # 构造 end = len(self._elems) for i in range(end//2, -1, -1): self.siftdown(self._elems[i], i, end) g = PriQueue([0, 20, 6, 7, 8, 9, 10]) g.bulidheap() print(g.peek())