优先队列之二叉堆

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;
}








  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值