欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

优先队列之二叉堆

程序员文章站 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;
}