优先队列是允许至少下列两种操作的数据结构:Insert(插入)以及DeleteMin(删除最小者),它的工作是找出、返回和删除优先队列中最小的元素。除了操作系统外,优先队列还有许多应用,比如堆排序、外部排序和贪婪算法。
优先队列是通过二叉堆实现的,二叉堆可简称为堆。同二叉查找树一样,堆也有两个性质,即结构性和堆排序。
结构性:堆是一棵被完全填满的二叉树,有可能的例外是在底层,底层上的元素从左到右填入。这样的树称为完全二叉树。因为完全二叉树很有规律,所以它可以用一个数组表示而不需要指针。对于数组任一位置i上的元素,其左儿子在位置2i上,右儿子在左儿子后的单元(2i+1)中,它的父亲则为位置[i/2]。这种实现方法的唯一问题在于,最大的堆大小需要你事先估计。
堆序性:使操作被快速执行得性质是堆序性。对于每一个节点X,X的父亲中的关键字小于(或等于)X中的关键字。
插入操作是通过上滤的策略将新元素在堆中上滤直到找到正确的位置,
在实现时,我们采用的是把一个很小的值放到位置0处以使while循环得以终止,我们称之为标记。
删除最小元操作是通过下滤策略来实现。注意:在实现时我们要避免直接交换操作。
优先队列的实现代码:
#include <stdio.h>
#include <stdlib.h>
#define MinData -1 //标识,比任何数据都小,假设所有的数据都大于0
typedef int ElementType;
typedef struct HeapStruct *PriorityQueue;
PriorityQueue Initialize(int MaxElements);
void Destroy(PriorityQueue H);
void Insert(ElementType X, PriorityQueue H);
ElementType DeleteMin(PriorityQueue H);
struct HeapStruct
{
int Capacity;
int Size;
ElementType *Elements;
};
PriorityQueue Initialize(int MaxElements)
{
PriorityQueue H;
H = (PriorityQueue)malloc(sizeof(struct HeapStruct));
if(H == NULL)
return NULL;
H->Elements = (ElementType *)malloc((MaxElements+1) * sizeof(ElementType));
if(H->Elements == NULL)
return NULL;
H->Elements[0] = MinData; //标识
H->Capacity = MaxElements;
H->Size = 0;
return H;
}
void Destroy(PriorityQueue H)
{
if(H != NULL)
{
free(H->Elements);
free(H);
}
}
void Insert(ElementType X, PriorityQueue H)
{
int i;
if(H->Size >= H->Capacity)
return;
//上滤过程
for(i = ++H->Size; H->Elements[i/2] > X; i/=2)
H->Elements[i] = H->Elements[i/2];
//填入元素
H->Elements[i] = X;
}
ElementType DeleteMin(PriorityQueue H)
{
ElementType minElement, LastElement;
int i, Child;
if(H->Size == 0)
return H->Elements[0];
minElement = H->Elements[1];
LastElement = H->Elements[H->Size--];
for(i = 1; i * 2 <= H->Size; i *= 2)
{
//从左右儿子中寻找更小的儿子
Child = i * 2;
if(Child < H->Size && H->Elements[Child+1] < H->Elements[Child])
Child++;
//下滤过程
if(H->Elements[Child] < LastElement)
H->Elements[i] = H->Elements[Child];
else
break;
}
//填入元素
H->Elements[i] = LastElement;
return minElement;
}
void print(PriorityQueue H)
{
for(int i=1; i <= H->Size; ++i)
printf("%d ", H->Elements[i]);
printf("\n");
}
int main()
{
PriorityQueue H;
H = Initialize(10);
if(H == NULL)
return 0;
printf("%d\n", DeleteMin(H)); //队列空时删除最小元
int element[10] = {10, 5, 45, 14, 123, 22, 33, 55, 66, 177};
for(int i=0; i<10; ++i)
Insert(element[i], H); //填满队列
print(H);
Insert(20, H); //队列满时插入元素
print(H);
printf("%d\n", DeleteMin(H)); //删除最小元
print(H);
system("pause");
return 0;
}
一个堆所蕴含的关于序的信息很少,因此,若不对整个堆进行线性搜索,是没有办法找出任何特定的关键字的。
还有其他的堆操作::
①DecreaseKey(降低关键字的值):DecreaseKey(P, △, H)操作降低在位置P处的关键字的值,降值的幅度为正的量△。通过上滤来调整堆序性。该操作对系统管理程序时有用的:系统管理程序能够使它们的程序以最高的优先级来运行。
②IncreaseKey(增加关键字的值):IncreaseKey(P, △, H)增加在位置P处的关键字的值,增值的幅度为正的量△,这可以通过下滤来完成。许多调度程序自动地降低正在过多地消耗CPU时间的进程的优先级。
③Delete(删除):Delete(P, H)操作删除堆中位置P的节点。这通过首先执行DecreaseKey(P, ∞, H),然后再执行DeleteMin(H)来完全。当一个进程被用户中止(而不是正常终止)时,它须从优先队列中除去。
④BuildHeap(构建堆):BuildHeap(H)操作把从N个关键字作为输入并把它们放入空堆中。这可以使用N个相继的Insert操作来完成,由于每个Insert将花费O(1)平均时间以及O(log N)的最坏情形时间,因此该算法的总的运行时间则是O(N)。