优先队列满足两种操作:insert和deletemin(deletemax),insert类似入队列,deletemin类似出队列,最小堆和最大堆是事先决定好的,这里全用最小堆
应用:操作系统进程、外部排序、贪婪算法
实现方式:
1.使用简单链表,表头O(1)插入,deletemin花费O(N),或者始终让链表排序,插入O(N),deleteminO(1),由于删除次数一定小于插入,因此采用O(1)插入,O(N)删除
2.使用二叉查找树,对两种操作都以O(logN)时间完成,缺陷是二叉查找树支持很多在优先队列中不需要的操作,增加了编码复杂性
3.二叉堆
结构性:是完全二叉树
堆序性:某个节点的儿子值比自己大
二叉堆代码如下:
#ifdef _cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <stdlib.h>
struct HeapStruct;
typedef struct HeapStruct * PriorityQueue;
typedef int ElementType;
PriorityQueue Initialize(int MaxElements);
void Destroy(PriorityQueue H);
void MakeEmpty(PriorityQueue H);
void Insert(ElementType X, PriorityQueue H);
ElementType DeleteMin(PriorityQueue H);
ElementType FindMin(PriorityQueue H);
int IsEmpty(PriorityQueue H);
int IsFull(PriorityQueue H);
struct HeapStruct
{
int Capacity;
int Size;
ElementType * Elements;
};
int IsEmpty(PriorityQueue H)
{
return 0 == H->Size;
}
int IsFull(PriorityQueue H)
{
return H->Size == H->Capacity;
}
/* 初始化优先队列,特定是有一个哑头,是一个比所有元素都小的元素 */
PriorityQueue Initialize(int MaxElements)
{
PriorityQueue H;
H = malloc(sizeof(struct HeapStruct));
H->Elements = malloc((MaxElements + 1)* sizeof(ElementType));
H->Capacity = MaxElements;
H->Size = 0;
H->Elements[0] = 0x80000000;
return H;
}
/* 空穴上冒法 */
void Insert(ElementType X, PriorityQueue H)
{
int i;
if (IsFull(H))
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)
{
int i, Child;
ElementType MinElement, LastElement;
if (IsEmpty(H))
return H->Elements[0];
MinElement = H->Elements[1];
LastElement = H->Elements[H->Size--];
for (i = 1; i * 2 <= H->Size; i = Child)
{
Child = i * 2;
/* 在两个儿子中找较小的 */
if (Child < H->Size && H->Elements[Child] > H->Elements[Child + 1])
++Child;
/* 若最后一个元素比较小的儿子都小,那么可以结束了,否则继续下滤 */
if (LastElement > H->Elements[Child])
H->Elements[i] = H->Elements[Child];
else
break;
}
H->Elements[i] = LastElement;
return MinElement;
}
/* 将位置为t的节点的值降低X */
void DecreaseKey(int t, ElementType X, PriorityQueue H)
{
int i, newElement = H->Elements[t] - X;
for (i = t; H->Elements[i / 2] > newElement; i /= 2)
H->Elements[i] = H->Elements[i / 2];
H->Elements[i] = newElement;
}
/* 将位置为t的节点的值增加X */
void IncreaseKey(int t, ElementType X, PriorityQueue H)
{
int i, Child;
int newElement = H->Elements[t] - X;
for (i = t; i * 2 <= H->Size; i = Child)
{
Child = i * 2;
if (Child < H->Size && H->Elements[Child] > H->Elements[Child + 1])
++Child;
if (newElement > H->Elements[Child])
H->Elements[i]= H->Elements[Child];
else
break;
}
H->Elements[i] = newElement;
}
/* 删除位置为t的节点,先上浮到跟,然后deletemin */
void Delete(int t, PriorityQueue H)
{
DecreaseKey(t, 0x7fffffff, H);
DeleteMin(H);
}
/* 将位置t的节点下滤到合适的位置,是BuildHeap的辅助函数 */
void PercolateDown(int t, PriorityQueue H)
{
int i, Child;
int Element = H->Elements[t];
for (i = t; i * 2 <= H->Size; i = Child)
{
Child = i * 2;
if (Child < H->Size && H->Elements[Child] > H->Elements[Child + 1])
++Child;
if (Element > H->Elements[Child])
H->Elements[i] = H->Elements[Child];
else
break;
}
H->Elements[i] = Element;
}
/* 将一个数组中的内容读取到优先队列 */
PriorityQueue BuildHeap(ElementType *a, int N)
{
int i;
PriorityQueue H = Initialize(N);
/* 将数字先全部插进去 */
for (i = 0; i < N; i++)
H->Elements[i + 1] = a[i];
/* 从中间的位置开始下滤,也就是倒数第二层 */
for (i = N / 2; i > 0; i /= 2)
PercolateDown(i, H);
}
void main()
{
}
#ifdef _cplusplus
}
#endif
优先队列解决选择问题:
选择问题是N个数中找第k小的,使用优先队列有两种方案
1.将N个数据建立一个最小堆,花费O(N),deletemin操作k次,这样第k次就是想要的结果,每次deletemin花费时间是O(logN),一共花费O(klogN + N)时间,若k接近N / 2,那么时间是O(NlogN)
2.抽取k个数据建立一个最大堆,花费O(k),然后将剩下的N - k个依次和堆的min进行比较,若大,那么不处理,否则deletemin然后插入,每次的花费是O(1)的比较和O(logk)的deletemin,一共花费O(k + (N - k)logK),若k接近N / 2,那么时间是O(NlogN)