1、模型。
优先队列和(队列与二叉树)有着相似的模型。通过对比二者可以更好的了解优先队列的模型。
优先队列 | 队列 | 二叉树 |
Insert | Enqueue | Insert |
DeleteMin | Dequeue
| Delete |
static int Succ(int Value, Queue Q)
{
if (++Value == Q->Capacity)Value = 0;
return Value;
}
void Enqueue(ElementType X, Queue Q)
{
if (IsFull(Q))FatalError("Full Queue");
else
{
Q->Size++;
Q->Rear = Succ(Q->Rear, Q);
Q->Array[Q->Rear] = X;
}
}
ElementType FrontAndDeQueue(Queue Q)
{
if (IsEmpty(Q))FatalError("Empty Queue.");
else
{
Q->Size--;
return Q->Array[Q->Front++];
}
}
以上的代码是队列中的入队和出队的函数的实现。
2、二叉堆的实现
二叉堆(Binary heap)是优先队列的实现中类似二叉树的地方。其具有结构性和堆序性。
1 )结构性。
堆是一棵完全充满的二叉树(complete binary tree),底层的元素若有空位,则从左向右插入。应当留意的是,这种数据结构的实现可以通过数组实现,同时正好填满整个数组。因此就避免了结构体和指针。
1 / \ 2 3 / \ / \ 4 5 6 7
以上的图中的数字表示的是数组的下标。可以看到,对于位置为 i 的元素,左儿子在位置 2i 上, 右儿子在 位置 2i + 1上,其父亲在位置⌊ i ⌋上。注意符号⌊⌋是向下取整的意思。
2)堆序性:对节点 X,X的关键字小于它的儿子,同时大于它的父亲。
代码表述为Elements[ Parent ]<Elements[ i ] <Elements[ Child ]
则根据这两个性质 ,可以实现对优先队列的基本操作。
#include<stdio.h>
#include<stdlib.h>
#define MinPQSize 5
typedef int ElementType;
struct HeapStruct {
int Capacity; //能存储的元素个数上限
int Size; //当前已有的元素个数
ElementType* Elements;
};
typedef struct HeapStruct* PriorityQueue;
void FatalError(const char* s)
{
printf("%s", s);
}
int IsFull(PriorityQueue H)
{
return H->Size == H->Capacity;
}
int IsEmpty(PriorityQueue H)
{
return H->Size == 0;
}
PriorityQueue Initialize(int MaxElements)
{
PriorityQueue H;
if (MaxElements < MinPQSize)FatalError("Priority queue Size too small.");
H = (PriorityQueue)malloc(sizeof(struct HeapStruct));
if (H = NULL)FatalError("Out of Space!");
H->Elements = (ElementType*)malloc(sizeof(ElementType) * (MaxElements + 1));
if (H->Elements == NULL)FatalError("Out of Space!");;
H->Capacity = MaxElements;
H->Size = 0;
H->Elements[0] = 0;
}
void Insert(ElementType X, PriorityQueue H)
{
int i;
if (IsFull(H))
{
FatalError("Priority Queue is full.");
return;
}
for (i = ++H->Size; H->Elements[i / 2] > X; i /= 2)
H->Elements[i] = H->Elements[i / 2];
/* 注意,假设本该插入到Elements[++H->Size]处的空穴,但是由于我们不能确定插入之后是否满足性质
Elements[(++H->Size)/2]<Elements[H->Size](注:此处在前方已经++H->Size,所以后面不用,也不能
再++了。)因此当其父节点的元素大于 X 时,将其放到其子节点处的空穴,同时在该处产生空穴。并再次循环
比较。其实我认为此处应该先将H->Elements[++H->Size]处的值赋为 X,并将其用来代替 X ,这样更容易理
解。(虽然这样在每次循环的过程中都需要赋值一次了)
*/
H->Elements[i] = X;
}
ElementType DeleteMin(PriorityQueue H)
{
//删除最小的节点就意味着删除根节点,由于父节点小于子节点的性质。
int i, Child;
ElementType MinElement, LastElement;
if (IsEmpty(H))
{
FatalError("PriorityQueue is empty.");
return H->Elements[0];
}
MinElement = H->Elements[1];
LastElement = H->Elements[H->Size--];
//注意,这里先将LastElement赋值为H->Elements[H->Size],再将Size--。
for (i = 1; i * 2 <= H->Size; i = Child)
{
Child = i * 2;
if (Child != H->Size && H->Elements[Child + 1] > H->Elements[Child])Child++;
//这一行是为了找到较大的儿子
if (LastElement > H->Elements[Child])H->Elements[i] = H->Elements[Child];
else break;
}
// 这个循环类似Insert里面的循环操作。只不过是将X改为了LasElement
H->Elements[i] = LastElement;
return MinElement;
}