优先队列之二叉堆
程序员文章站
2022-06-06 20:56:05
...
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 *xin 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 *xin 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 *xin 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;
}
上一篇: 一种二叉堆实现