优先队列(堆)
优先对列数据模型至少需要允许一下两种操作:1.Insert(插入);2.DeleteMin(删除最小者)
最简单的实现方法是使用链表来实现,在表头进行插入,并遍历该链表进行删除最小元。插入以O(1)完成,但是删除将会是O(N)的时间花费。因此,我们需要别的实现方法
二叉堆
二叉堆具有两种性质
- 结构性质:底层上的元素从左到右填充,,即完全二叉树。因此位置i的左儿子为2i,右儿子为2i+1.因此可以不使用指针,用数组实现即可。
2.堆序性质:为了最快速的找到最小元,因此任意节点都小于它的所有后裔。
基本操作
- Insert(插入)
//H->Elements[0] is a sentinel
void Insert( int X, PriorityQueue H )
{
int i;
if( IsFulll( H ) )
{
printf("Priority is full
");
return;
}
for( i = ++H->Size; H->Elements[i/2] > X; i /= 2 )
H->Elements[i] = H->Elements[i/2];
H->Elements[i] = X;
}
插入操作消耗,最坏情况消耗O(logN)
- DeleteMin(删除最小元)
int DeleteMin( PriorityQueue H )
{
int i,Child;
int MinElement,LastElement;
if( IsEmpty( H ))
{
printf("Priority queue is empty.
");
return H->Elements[0];
}
MinElement = H->Elements[1];
LastElement = H->Elements[H->Size--];
for( i = 1; i * 2 <= H->Size; i = Child )
{
//Find smaller child
Child = i * 2;
if( Child != H->Size && H->Elements[Child + 1] < H->Elements[Child])
Child++;
//Percolate one level
if( LastElement > H->Elements[Child])
H->Elements[i] = H->Elements[Child];
else
break;
}
H->Elements[i] = LastElement;
return MinElement;
}
最坏情况和平均运行时间均为 O(logoN)
其他操作
- DecreaseKey(降低关键字的值):DecreaseKey(P, delta,H ),需要进行上滤操作
- IncreaseKey(增加关键字的值)
- Delete(删除):删除位置P上的节点。首先执行DecreaseKey(P,infinity,H),然后执行DeleteMin(H)
- BuildHeap(构建堆):将N个关键字以任意顺序放入树中,保持结构特性,然后进行下滤操作。
d-堆
二叉堆就是d=2的d-堆,同理可以推出3-堆,4-堆等。其特点是减少了Insert的时间,但是加大了DeleteMin的时间。
左式堆
零路径长(null path length,NPL),Npl(X)定义为从X到一个没有两个儿子的节点的最短的路径的长。因此,拥有0个或者1个儿子的节点的Npl为0,而Npl(NULL)= -1.
左式堆的性质是:对于堆中的每一个节点X,左儿子的零路径长至少是右儿子零路径长一样大。
左式堆的基本操作是合并。其合并过程为递归实现的。
另因为其用到了指针,所以在与二叉堆的兼容过程中用到了宏。
斜堆
与左式堆各种相同,不同之处在于:对于左式堆,我们查看是否左儿子与右儿子满足左式堆堆序性质并交换那些不满足该性质的;但对于斜堆,除了这些右路径上所有节点最大者不交换他们的左右儿子外,交换是无条件的。(其实,最大者根本就没有儿子,也就不用交换了)
左式堆和斜堆每次操作花费时间是O(logN),有效的支持了合并、插入和DeleteMin。
二项队列
二项队列不是一棵树,而是堆序树的集合,称为森林。堆序树中的每一棵树都是有约束的形式,称之为二项树。