这一章讲的是优先队列,实现优先队列有各种方式,二叉堆是比较常见的。
系统管理中有的程序优先级比较高,有时我们要调整某个程序的优先级或者有时需要结束某个进程,这种操作无法借助队列(先进先出)完成。我们提出了优先队列(priority queue)。优先队列是至少允许insert(插入,类似于队列中的入队)和deleteMin(删除最小者,等价于队列中的出队,功能是找到、返回、删除队列中的最小元素)这两种操作的数据结构。这么说满足这两种操作的就可以称为优先队列了。
那么我们要考虑的是实现起来的效率。简单的实现方式,我们可以用链表实现,第一种是以O(1)插入,那么deleteMin的代价是O(N),我们需要遍历链表。第二种是让表保持排序的状态,这样子deleteMin的代价是O(1),插入的代价是O(N)。考虑到deleteMin的次数不多于insert,前者似乎要好一点。还可以用二叉查找树,这两种操作的代价是O(logN)。但二叉查找树还支持其他很多操作,将它用于优先队列就是杀鸡用牛刀了。不如把表和树结合起来吧,用表的形式存储数据,用树的方式操作,这就是二叉堆了,存储数据用的是数组,操作时把它当成树。可是这怎么能做到呢?我们借助了完全二叉树,就是完全被填满的二叉树,底层的元素从左到右。
完全二叉树可以用在优先队列是因为它很有规律:对于数组中位置为i的元素,其左儿子在2i处,右儿子在2i+1处,其父亲在i/2处。另外,我们可以证明,高为h的二叉树有2^h ~ 2^(h+1)-1个节点,也就是说完全二叉树的高为logN,这为执行的效率提供了保障。
如果我们想要快速的执行deleteMin,那么最好让最小元素在根节点。根据递归的思想,把每个子树看成一个二叉堆,那么我们需要保证任意节点都会小于他的所有孩子。上面两点就是二叉堆的结构性和堆序性,我们在操作时要保持这两种性质。
对于插入操作,插入一个新元素时,会破坏堆序性,我们采用上滤(percolate up)维持这种性质。具体一点就是在最后的位置留一个空位(空穴),比较这个空位的父节点元素和要插入的元素的大小,如果父节点元素较大,则将父节点元素放入空穴,此时父节点的位置成了空穴。一直向上直到要插入的元素不小于父节点元素,则将插入元素放入空穴中。(详见代码)。
删除最小元的操作,最小元在根节点处,我们删掉了这个节点的元素,根节点就是个空穴了,我们要采用下滤的方法把这个空穴填满,不能破坏结构性。和上滤差不多,比较节点的两个孩子,把较小的孩子放在这个节点处,那么孩子节点就空出来了,一直向下。要注意的是,我们要使得最后树的底层元素是从左到右填充的,所以删除一个元素后,表现在树的结构上就是最后一个元素没了,我们要把最后一个元素放在某个合适的位置,下面还是给一个例子吧
原 下滤一次
下滤两次 最后一个元素放到空穴中
再来一个例子
原 过滤一次
最后一个元素放入空穴 结果
另外有一些操作,比如降低/增加关键字的值,就可以使系统管理程序中的某个程序改变优先级,删除节点可以通过降低关键字优先级再deleteMin,当进程被用户中止时,就需要从队列中删除了。
稍微提一下d-堆,d-堆是二叉堆的简单推广,不同之处在于d-堆的所有节点都有d个儿子,如此看来,二叉堆是d-堆的一种,为2-堆。d-堆比二叉堆要浅,insert的代价为O(),deleteMin由于下滤时需要比较所有儿子的大小,每次花费d-1次比较,时间代价为O(
),当d为常数时,时间还是O(logN),当d很大时,如d=N/2,那时间就不再是O(logN)了。一般会选择d为2的幂,可以通过移位进行除法从而找到父节点的位置,否则就要进行乘除,大大增加了运行时间。
代码实现二叉堆:
BinHeap.h
#pragma once
#include<iostream>
using namespace std;
#define MINDATA -32767
#define MINSIZE 10
struct HeapStruct;
typedef int ElementType;
typedef HeapStruct *BinHeap;
BinHeap initialize(unsigned int maxSize);
void makeEmpty(BinHeap h);
void destroy(BinHeap h);
void insert(BinHeap h, ElementType e);
ElementType findMin(BinHeap h);
ElementType deleteMin(BinHeap h);
bool isFull(BinHeap h);
bool isEmpty(BinHeap h);
void percolateDown(BinHeap h, unsigned int i);
void buildHeap(BinHeap h);
void printHeap(BinHeap h);
struct HeapStruct
{
ElementType *data;
unsigned int capacity;
unsigned int heap_size;
};
BinHeap.cpp
#include "stdafx.h"
#include "BinHeap.h"
BinHeap initialize(unsigned int max_size)
{
if (max_size<MINSIZE)
{
cerr << "heap size is too small" << endl;
return nullptr;
}
BinHeap heap = (BinHeap)malloc(sizeof(HeapStruct));
if (heap==nullptr)
{
cerr << "out of space" << endl;
return nullptr;
}
heap->data = (ElementType*)malloc(sizeof(ElementType)*(max_size + 1));
if (!heap->data)
{
free(heap);
cerr << "out of space" << endl;
return nullptr;
}
heap->capacity = max_size;
heap->heap_size = 0;
heap->data[0] = MINDATA;
return heap;
}
void makeEmpty(BinHeap h)
{
if (h)
{
h->heap_size = 0;
}
}
void destroy(BinHeap h)
{
if (h)
{
if (h->data)
{
free(h->data);
}
free(h);
}
}
void insert(BinHeap h, ElementType e)
{
if (isFull(h))
{
return;
}
unsigned int i;
for (i=h->heap_size+1;h->data[i/2]>e;i/=2)//上滤
{
h->data[i] = h->data[i / 2];
}
h->data[i] = e;
}
ElementType findMin(BinHeap h)
{
if (isEmpty(h))
{
cerr << "empty heap" << endl;
return MINDATA;
}
return h->data[1];
}
ElementType deleteMin(BinHeap h)//下滤,最后一个元素放置的位置
{
unsigned int i, child;
if (isEmpty(h))
{
cerr << "empty heap" << endl;
return MINDATA;
}
ElementType min_ele = h->data[1];
ElementType last_ele = h->data[h->heap_size--];//取得最后一个元素并将size-1
for (i =1;i*2 <=h->heap_size;i=child)
{
child = 2 * i;
if (child!=h->heap_size&&h->data[child]>h->data[child +1])//child!=h->heap_size
{
child++;
}
if (last_ele>h->data[child])//让最后一个元素插入到合适的位置
{
h->data[i] = h->data[child];
}
else
{
break;
}
}
h->data[i] = last_ele;
return min_ele;
}
bool isFull(BinHeap h)
{
return h->capacity == h->heap_size;
}
bool isEmpty(BinHeap h)
{
return h->heap_size == 0;
}
void percolateDown(BinHeap h, unsigned int i)
{
unsigned left, right,min_index;
left = i << 1;
right = left + 1;
min_index = i;
if (left<=h->heap_size&&h->data[left]<h->data[min_index])
{
min_index = left;
}
if (right <= h->heap_size&&h->data[right] < h->data[min_index])
{
min_index = right;
}
if (min_index!=i)
{
swap(h->data[min_index], h->data[i]);
percolateDown(h, min_index);
}
}
void buildHeap(BinHeap h)
{
ElementType input;
unsigned int i = 1;
cout << "input: (less tahn " << h->capacity << ")" << endl;
while (i <= h->capacity)
{
cin >> input;
if (input==-1)
{
break;
}
h->data[i] = input;
h->heap_size++;
++i;
}
for (unsigned int i=h->heap_size/2;i>0;--i)
{
percolateDown(h, i);
}
}
void printHeap(BinHeap h)
{
for (int i=1;i<=h->heap_size;++i)
{
cout << h->data[i] << " ";
}
cout << endl;
}
test.cpp
#include "stdafx.h"
#include "BinHeap.h"
int main()
{
BinHeap heap = initialize(30);
buildHeap(heap);
printHeap(heap);
deleteMin(heap);
printHeap(heap);
destory(heap);
return 0;
}
结果