1.优先队列简介
1.1模型
优先队列是允许至少下列两种操作的数据结构:Insert(插入),它的工作是显而易见的,以及DeleteMin(删除最小者),它的工作是找出、返回和删除优先队列中最小的元素。Insert操作等价于Enqueue(入队),而DeleteMin则是队列中的Dequeue(出队)在优先队列中的等价操作。优先队列的基本模型如下图。
1.2 一些简单的实现
有几种明显的方法实现优先队列。我们可以使用一个简单链表在表头以O(1)执行插入操作,并遍历该链表以删除最小元,这有需要O(N)时间。另一种方法是,始终让表保持排序状态;这使得插入的代价高昂(O(N))而DeleteMin花费低廉(O(1))。
再一种实现优先队列的方法是使用二叉查找树。它对这两种操作的平均运行时间都是O(log N)。但是查找树可能有些过分,因为它支持许许多多并不需要的操作。下面我们将介绍二叉堆,使用数组实现。还有便于合并的左式堆,它用指针实现。另外我们将介绍二项队列。它是二项树的集合。
2.二叉堆
二叉堆是优先队列的一种实现。它具有两个性质,即结构性和堆序性。
2.1 结构性质
堆是一课被完全填满的二叉树,有可能的例外是在底层,底层上的元素从左到右填入。这样的树称为完全二叉树,如下图。
下面是是完全二叉树的数组表示。对于数组中任一位置上的元素,其左儿子在位置2i上,右儿子在 2i + 1上。它的父亲在i/2上。注意位置0是不存储元素的。根存储在位置1。
2.2 堆序性质
使操作被快速执行的性质是堆序性。由于我们想要快速地找出最小元,因此最小元应该在根上。如果我们考虑任意子树也应该是一个堆。那么任意节点就应该小于它的后裔。
2.3 基本的堆操作
2.3.1 Insert(插入)
为将一个元素X插入到堆中,我们在下一个空闲位置创建一个空穴,否则就该堆就不是完全树。如果X可以放在空穴中而并不破坏堆的序,那么插入完成。否则,我们将空穴的父节点的元素移入该空穴中,这样,空穴就朝着根的方向上行一步。继续该过程直到X可以放到空穴为止。下面四个图是插入14的过程。为了插入14,我们在堆的下一个可用位置建立一个空穴。由于14插入空穴破坏堆序性质,因此将31移入该空穴。继续这种操作直到找到一个位置可以放置14而不破坏堆序性。
这种策略叫做上滤(percolate up)。
2.3.2 DeleteMin(删除最小元)
DeleteMin以类似于插入的方式处理,找出最小元是容易的。困难的是部分是删除它。采取的策略叫下滤(percolate down)。当删除根节点时,此处产生一个空穴。我们将空穴的两个左右儿子中较小的者移入空穴。这样空穴就向下推了一层。重复该步骤直到X可以被放入空穴中。下面的图表示这个过程。
2.3.4 其他的堆操作
(1)DecreaseKey(降低关键字的值)
DecreaseKey(P,∆,H)操作降低在位置P处的关键值,降低的幅度为正的量∆。采用上滤堆堆进行调整。
(2)IncreaseKey(增加关键字的值)
IncreaseKey(P,∆,H)操作增加在P处的关键值。增值的幅度为正的量。采用下滤来完成。
(3)Delete(删除)
Delete(P,H)操作删除堆中位置P上的节点。首先通过执行DecreaseKey(P,∞,H),然后执行DeleteMin(H)来完成。
(4)BuildHeap(构建堆)
BuildHeap(H)操作把N个关键字作为输入并把它们放入空堆中。一般的算法是将N个关键字以任意顺序放入到树中。保持结构特性。此时,如果percolateDown(i)从节点下滤,那么执行以下算法创建一棵具有堆序的树。
for(i = N/2;i > 0;i--)
percolateDown(i);
2.4 代码实现
2.4.1 头文件
//
// BinHeap.h
// PriorityQueue
//
// Created by Wuyixin on 2017/5/29.
// Copyright © 2017年 Coding365. All rights reserved.
//
#ifndef BinHeap_h
#define BinHeap_h
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define PRIORITY_QUEUE_SIZE_MIN 50
struct HeapStruct;
typedef struct HeapStruct *PriorityQueue;
typedef int ElementType;
extern const int DATA_MIN;//最小值,用来标记空节点
/* 初始化 */
PriorityQueue init_queue(int max_elements);
/* 构建堆操作,把n个关键字(任意顺序)作为输入并把它们放入到空堆中*/
PriorityQueue build_heap(PriorityQueue h ,ElementType arr[],unsigned int n);
/* 销毁堆 */
void destroy(PriorityQueue h);
/* 置空堆 */
void make_empty(PriorityQueue h);
/* 插入 */
void insert(ElementType x,PriorityQueue h);
/* 删除最小 */
ElementType delete_min(PriorityQueue h);
/* 删除元素 */
ElementType delete_element(int p ,PriorityQueue h);
/* 查找最小 */
ElementType find_min(PriorityQueue h);
/* 降低关键字的值 */
ElementType decrease_key(int p,ElementType d,PriorityQueue h);
/* 增加关键字的值 */
ElementType increase_key(int p,ElementType d,PriorityQueue h);
/* 是否空 */
int is_empty(PriorityQueue h);
/* 是否满 */
int is_full(PriorityQueue h);
/* 打印堆 */
void print_queue(PriorityQueue h);
struct HeapStruct
{
int capacity;
int size;
ElementType *elements;
};
#endif /* BinHeap_h */
2.4.2 实现文件
// // BinHeap.c // PriorityQueue // // Created by Wuyixin on 2017/5/29. // Copyright © 2017年 Coding365. All rights reserved. // #include "BinHeap.h" const int DATA_MIN = INT_MIN; static int percolate_up(int p,ElementType value,PriorityQueue h); static int percolate_down(int p,ElementType value,PriorityQueue h); static void error(char* message){ printf("%s\n",message); } static void fatal_error(char* message){ error(message); exit(EXIT_FAILURE); } /* 上滤 */ /* 一个元素的值降低,或者在堆的末尾增加新值,都会用到此操作。此操作的特点是让节点“往上升” */ /* return 返回上滤后的新位置*/ static int percolate_up(int p,ElementType value,PriorityQueue h){ if (is_empty(h)) return DATA_MIN; if (p < 1 || p > h->size) return h->elements[0]; /* 由于h->elements[0]的值是DATA_MIN,所以可以作为循环终止的条件 */ int i; for (i = p; value < h->elements[i/2]; i /= 2) { h->elements[i] = h->elements[i/2]; } return i; } /* 下滤 */ /* 一个元素的值增加,或者删除堆的最小元(根),都会用到此操作。此操作的特点是让节点“往下降” */ static int percolate_down(int p,ElementType value,PriorityQueue h){ if (is_empty(h)) return DATA_MIN; if (p < 1 || p > h->size) return h->elements[0]; int i; int child; for (i = p; 2 * i <= h->size; i = child) { child = 2 * i; /* 比较左右孩子,取出小的元素上滤*/ /* 直到"空穴"来到最后一层(树叶)或者 “空穴”的两个孩子都比last_elem的值要大,找到这个位置*/ if (child != h->size && h->elements[child] > h->elements[child + 1]) child++; if (value > h->elements[child]) h->elements[i] = h->elements[child]; else break; } return i; } PriorityQueue init_queue(int max_elements){ /* 给堆分配的长度太小 */ if (max_elements < PRIORITY_QUEUE_SIZE_MIN) error("Priority queue size is too small"); PriorityQueue h = malloc(sizeof(struct HeapStruct)); if (h == NULL) fatal_error("Out of space!!!"); /* 数组第一个元素不存储节点,根从第二个元素开始,因为这样节点的父亲、左孩子、右孩子的表达式比较清楚。因此实际分配的大小要比max_elements大1 */ h->elements = malloc((max_elements + 1) * sizeof(ElementType)); if (h->elements == NULL) fatal_error("Out of space!!!"); h->size = 0; h->capacity = max_elements; h->elements[0] = DATA_MIN;/* 做标记用 */ return h; } PriorityQueue build_heap(PriorityQueue h ,ElementType arr[],unsigned int n){ if (h == NULL) return h; if (n == 0) return h; int i = 0; /* 将数组中的元素的值复制到堆中(无序) */ ElementType* p = h->elements + 1; while (i < n) *p++ = arr[i++],h->size++; i = n / 2; while(i > 0) percolate_down(i, h->elements[i], h),i--; return h; } void destroy(PriorityQueue h){ free(h->elements); free(h); } void make_empty(PriorityQueue h){ /* 其实就是把size变成零,虽然堆里的每个节点的值还在,但都是无效的。 */ h->size = 0; } void insert(ElementType x,PriorityQueue h){ int i; if (is_full(h)) error("Priority queue is full"); /* 插入元素先放在堆的末尾,然后经过上滤的过程找到合适的位置并插入 */ /* 注意++h->size已经把size加1 */ i = percolate_up(++h->size, x, h); h->elements[i] = x; } ElementType delete_min(PriorityQueue h){ if (is_empty(h)) return DATA_MIN; /* 注意h->size--已经把size减1 */ ElementType last_elem = h->elements[h->size--]; ElementType min_elem = h->elements[1]; /* 把最小元删除,经过下滤过程,把堆序重新调整*/ int i = percolate_down(1, last_elem, h); if (i != DATA_MIN) h->elements[i] = last_elem; return min_elem; } /* 删除位置p的节点。这通过首先执行decrease_key(p,∞,h),然后在执行delete_min(h)来完成*/ ElementType delete_element(int p ,PriorityQueue h){ if (is_empty(h)) return DATA_MIN; if (p < 1 || p > h->size) return h->elements[0]; ElementType elem = h->elements[p]; decrease_key(p, INT_MAX, h); delete_min(h); return elem; } ElementType find_min(PriorityQueue h){ if (is_empty(h)) return h->elements[0]; return h->elements[1]; } /* 该操作降低位置p处的关键值,降值的幅度为正的量d。由于这可能破坏堆的序,因此必须通过上滤对堆进行调整 */ ElementType decrease_key(int p,ElementType d,PriorityQueue h){ if (is_empty(h)) return INT_MIN; if (p < 1 || p > h->size) return h->elements[0]; if (d <= 0) return h->elements[0]; ElementType value = h->elements[p] - d; int i = percolate_up(p, value, h); h->elements[i] = value; return value; } /* 该操作增加位置p处的关键值,增值的幅度为正的量d。由于这可能破坏堆的序,因此必须通过下滤对堆进行调整 */ ElementType increase_key(int p,ElementType d,PriorityQueue h){ if (is_empty(h)) return INT_MIN; if (p < 1 || p > h->size) return h->elements[0]; if (d <= 0) return h->elements[0]; ElementType value = h->elements[p] + d; int i = percolate_down(p, value, h); h->elements[i] = value; return value; } int is_empty(PriorityQueue h){ return h->size == 0; } int is_full(PriorityQueue h){ return h->size >= h->capacity; } void print_queue(PriorityQueue h){ if (!is_empty(h)){ int i = 0; while (i++ < h->size) printf("%d ",h->elements[i]); } }
2.4.3 调用
//
// main.c
// PriorityQueue
//
// Created by Wuyixin on 2017/5/29.
// Copyright © 2017年 Coding365. All rights reserved.
//
#include <stdio.h>
#include "BinHeap.h"
int main(int argc, const char * argv[]) {
PriorityQueue h = init_queue(20);
int arr[] = {1,2,3,4,5,6,7,8,9,10};
build_heap(h, arr, 10);
insert(-1, h);
delete_min(h);
decrease_key(2, 18, h);
increase_key(10, 18, h);
delete_element(6, h);
print_queue(h);
make_empty(h);
destroy(h);
return 0;
}