堆和优先队列 优先队列( 三 )


最后将两个B2堆合并成一个新队列中的B3堆 。
二项队列的deleteMin很简单,只需要比较队列中所有二项堆的根节点,返回和删除最小的值即可,时间复杂度为O(logN),然后进行一次merge操作,也可以使用一个单独的空间每次记录最小值,这样就可以以O(1)的时间返回 。
森林中树的实现采用“左子右兄弟”的表示方法,然后二项队列可以使用一个数组来记录森林中每个树的根节点 。
例如上边的合成的二项队列可以表示成如下的样子:
STL中,二叉堆是通过 priority_queue 模板类实现的,在头文件 queue 中,STL实现一个大顶堆而不是小顶堆,其关键的成员函数如下:
优先队列(PriorityQueue) 在数据结构中,普通的队列是先进先出,但有时我们可能并不想有这么固定的规矩,我们希望能有一个带优先级的队列 。考虑在现实生活中,一些服务排队窗口会写着“军人依法优先”;送进医院的患者,即便是按顺序到达的,生病更加严重的往往优先级也会更高;还有操作系统中的作业调度也和优先级有关......
于是我们能不能改进队列?使得队列是有一定优先级的,这样能让一些事物和任务的处理变的更加灵活 。当然是可以的,最基本的我们可以基于线性结构来实现,考虑基于线性结构的时间复杂度:
1、队列是一种FIFO(First-In-First-Out)先进先出的数据结构,对应于生活中的排队的场景,排在前面的人总是先通过,依次进行 。
2、优先队列是特殊的队列,从“优先”一词,可看出有“插队现象” 。比如在火车站排队进站时,就会有些比较急的人来插队,他们就在前面先通过验票 。优先队列至少含有两种操作的数据结构:insert(插入),即将元素插入到优先队列中(入队);以及deleteMin(删除最小者),它的作用是找出、删除优先队列中的最小的元素(出队) 。
结构\操作入队出队
普通线性结构O(1)O(n)
顺序线性结构O(n)O(1)
普通线性结构实现的优先队列出队时间复杂度是O(n),因为出队要拿出最优先的元素,也就是相对最大的元素(注意:大小是相对的,我们可以指定比较规则),从而要扫描一遍整个数组选出最大的取出才行 。而对于顺序线性结构的入队操作,入队后可能破坏了原来的有序性,从而要调整当前顺序 。
可以看到使用线性结构总有时间复杂度是O(n)的操作,还有没有更好的实现方式呢,当然是有的,这就要来聊一聊堆Heap 。
堆严格意义上来说又叫二叉堆(Binary Heap),因为它的结构是一颗完全二叉树,堆一般分为最大堆和最小堆 。
堆性质:
结构性:堆是一颗除底层外被完全填满的二叉树,底层的节点从左到右填入,这样的树叫做完全二叉树 。即缺失结点的部分一定再树的右下侧 。
堆序性:由于我们想很快找出最小元,则最小元应该在根上,任意节点都小于它的后裔,这就是小顶堆(Min-Heap);如果是查找最大元,则最大元应该在根上,任意节点都要大于它的后裔,这就是大顶堆(Max-heap) 。
最大堆:父亲节点的值大于孩子节点的值
最小堆:父亲节点的值小于孩子节点的值
由于是完全二叉树,节点的索引之间有着一定的关系,故我们可以使用数组来存储二叉堆,具体如下:
如果从索引为0开始存储,则父亲和孩子节点的索引关系如下:
当我们需要向一个最大堆添加一条新的数据时,此时我们的堆变成了这样 。
此时,由于新数据的加入已经不符合最大堆的定义了 。所以我们需要对新加入的数据进行shift up操作,将它放到它应该在的位置 。shift up操作时我们将新加入的数据与它的父节点进行比较 。如果比它的父节点大,则交换二者 。
此时我们就完成了 对新加入元素的shift up操作 。
当我们从堆中(也就是优先队列中)取出一个元素时 。我们是将堆顶的元素弹出 。(只能从堆顶取出)
此时这个堆没有顶了,那么该怎么办呢?我们只需要把这个堆最后一个元素放到堆顶就可以了 。
此时我们就完成了弹出一个元素之后的shift down操作 。
replace:去除最大元素后,放入一个新元素
实现:可以先extractMax,再add,两次longn操作 。
实现:将堆顶的元素替换以后sift down,一次O(logn)操作
将n个元素逐个插入到一个空堆中,算法复杂度是O(nlogn),heapify的过程,算法的复杂度为O(n).
heapify:将任意数组整理成堆的形状 。
首先将一个数组抽象成一个堆 。这个过程,我们称之为heapify 。

秒懂生活扩展阅读