一、描述

其实就是一个数组,它被看做是一个近似完全二叉树,树上的每个元素的每个节点对应数组中的一个元素。除了最底层没有填充完,该树是完全的,而且是从左向右填充。

表示堆的数组包括两个属性:
A.length 通常表示数组元素的个数
A.heap_size 通常表示有多少个堆元素存储在数组中

也就是说 A[1 … A.length] 可能都存有数据,但是只有 A[1 … A.heap_size] 存放的是堆的有效元素。
这里:

0 <= A.heap_size <= A.length

这里,树根为 A[1]
给定一个下标我们就能很容易计算出它的父节点、左孩子、右孩子的下标

PARENT(i)
    return i/2

LEFT(i)
    return 2*i

RIGHT(i)
    return 2*i + 1

这里一般可以用宏或者 inline 内联函数来实现。


二、最小堆与最大堆

最大堆中,最大堆性质指的是:除了根节点之外的所有节点都要满足

A[PARENT(i)] >= A[i]

最小堆中,最小堆性质指的是:反过来

A[PARENT(i)] <= A[i]

最小堆通常用于构造优先队列


如果把堆看成一棵树,我们定义一个堆中的节点的高度
该节点到叶子节点最长简单路径上边的数目 ;进而我们可以将堆的高度定义为根节点的高度

那么堆是一个包含 n 个节点的完全二叉树,其高度就是

O( lg(n) )

其实堆上面的一些操作,其时间至多和堆的高度成正比,即 o(log(n))

基本操作如下:

MIN_HEAPIFY :  O(lg(n)),它自上而下维护(这里讨论最小堆)堆的性质
BUILD_MIN_HEAP : 线性时间复杂度,从无序输入数据(数组)中构造一个最小堆
HEAP_SORT : 堆排序,时间复杂度O( n*lg(n) ),对一个数组进行原址排序
MAX_HEAP_INSERT / HEAP_EXTRACT_MIN / HEAP_DECREASE_KEY / HEAP_MINIMUM : 时间复杂度都是O(lg(n)),功能是利用堆实现一个优先队列。

三、维护堆的性质

MIN_HEAPIFY 是维护堆性质的关键。输入是数组 A 和下标 i
调用它的时候,我们假定根节点 LEFT( i ) RIGHT( i ) 的二叉树都是最小堆,这时 A[ i ] 可能大于其孩子,违背了最小堆的性质。 MIN_HEAPIFY 通过让 A[ i ] 的值逐级下降,从而使得以 i 为根节点的子数遵循最小堆的性质。

伪代码如下:

// suppose that the left & right children are already meet min-heap
// modify the location of i inorder to meet the requirement
//
MIN_HEAPIFY(A, i)
{
    left = LEFT(i) 
    right = RIGHT(i)

    if (left < A.heap_size && A[left] < A[i])
        minimal = left
    else
        minimal = i

    if (right < A.heap_size && A[right] < A[i])
        minimal = right

    if (minimal != i) {
        exchange A[i] with A[minimal]

        MIN_HEAPIFY(A, minimal)     //recurse
    }
}

//
//
MIN_HEAPIFY_LOOP(A, i)
{
    left = LEFT(i)
    right = RIGHT(i)

    if (left < A.heap_size && A[left] < A[i])
        minimal = left
    else
        minimal = i

    if (right < A.heap_size && A[right] < A[i])
        minimal = right

    while (minimal != i) {      // loop instead of recurse
        exchange A[i] with A[minimal]

        i = minimal
        l = LEFT(i)
        r = RIGHT(i)
        if (l < A.heap_size && A[l] < A[i])
            minimal = l
        else
            minimal = i

        if (r < A.heap_size && A[r] < A[i])
            minimal = r
    }
}

第一个版本是递归版,第二个用普通的循环代替递归操作。

时间复杂度:

T( n ) = O( lg n )

四、建堆

我们可以用自底向上的方法利用过程 MIN_HEAPIFY 把一个大小为 n = A.length 的数组 A[ 1 … n ]转换为最大堆。

观察可以知道,字数组中 A[ (n/2)+1 … n ] 这些元素都是树的叶子节点,每个叶子节点可以看做是只包含一个元素的堆。
因此,build 堆的过程从最底下的第一个非叶子节点开始就可以了,然后自底向上 到根节点,不断调整以当前节点为根的子树的最小(或者最大)堆性质:

// A is the input data array
// from n/2 , bottom to top
//
BUILD_MIN_HEAP(A)
{
    A.heap_size = A.length
    for i = (A.length / 2) to 1
        MAX_HEAPIFY(A, i) 
}

时间复杂度:

O( n )

五、堆排序算法

初始的时候,堆排序算法利用 BUILD_MIN_HEAP 将输入 A[ 1 … n ] 建立为最小堆,n = A.length

数组中最大元素在 A[ 1 ] 之中,通过把它与 A[ n ] 进行互相交换,可以让元素放到正确的位置上去,然后去掉这个点 n (可以通过减少 A.heap_size)来实现。

交换到 A[ 1 ] 的节点可能不符合最小(或者最大)堆的性质,因此我们调整 MIN_HEAPIFY(A , 1) ,从而在 A [ 1 … n-1 ] 上再次构造一个最小(大)堆

堆排序算法不断重复这个过程,直到堆的大小从 n - 1 降到 2

HEAPSORT(A)
{
    BUILD_MIN_HEAP(A);

    for i = A.length downto 2
    {
        exchange A[1] with A[i]     //A[1] store the minimal/largest

        A.heap_size = A.heap_size - 1
        MIN_HEAPIFY(A, 1)
    }
}

时间复杂度:

O( n * lg(n) )

六、优先队列

优先队列是一种用来维护由一组元素构成的集合S的数据结构,其中每个元素都有一个相关值,称作‘关键字’

一个“最小优先队列”支持以下操作:

INSERT(S, x) : 将元素x插入结合S之中
MINIMUM(S) : 返回S中具有最小关键字的元素
EXTRACT_MIN(S) : 去掉并返回S中的具有最小关键字的元素
DECREASE_KEY(s, x, k) : 将元素 x 的关键字减少到 k ,这里是针对最小堆, k <= x 才行。 
            如果是最大堆,这里应该是增加关键字, k >= x

应用点:

“最大优先队列”的应用有很多,其中一个就是在共享计算机系统的作业调度中,记录将要执行的各个作业以及他们之间的相对优先级。当一个作业完成或者被中断后,调度器调用 EXTRACT_MAX 从所有等待的作业中,取出具有最高优先级的作业来执行。在任何时候都能够调用 INSERT 将一个新作业加入到队列中来。

相应的,“最小优先队列”可以被用于基于事件驱动的模拟器。队列中保存要模拟的时间,每个事件都有一个发生事件作为其关键字。事件必须按照发生的时间顺序进行模拟,因为某一个事件的模拟结果可能会触发对其他事件的模拟。在每一步中,模拟程序调用 EXTRACT_MIN 来选择下一个要模拟的事件。新的事件产生时,模拟器通过调用 INSERT 将它插入到最小优先队列中。

HEAP_MINIMUM(A)在 O(1)时间获得最小值:

#
# priority queue: based on minimal-heap
#


// get the minimal element of the array A
//
HEAP_MINIMAL(A)
{
    return A[1]
}

HEAP_EXTRACT_MIN 实现 EXTRACT_MIN 操作。它与 HEAP_SORT的 for 循环部分很相似。
时间复杂度: O( lg n )

// get the minimal, delete it from the origin data set
//
HEAP_EXTRACT_MIN(A)
{
    if (A.heap_size < 1)
        error: heap underflow

    min = A[1]
    A[1] = A[A.heap_size]
    A.heap_size = A.heap_size - 1

    MIN_HEAPIFY(A, 1)       // adjust 维持堆的性质

    return min
}

HEAP_DECREASE_KEY 实现 DECREASE_KEY 的操作

在优先队列中,我们希望减少/增加 关键字的优先队列元素由对应的数组下标 i 来标识。这一操作需要首先将元素 A[ i ] 的关键字更新为新的值。而减少/增加 关键字的值之后,可能会违反堆的性质,从当前节点到树的根节点的路径上(自下而上),需要不断为新增的关键字寻找适当的插入位置,不断与它的父节点元素进行比较和交换,直到它符合最小/最大堆的性质为止。

// in minimal-heap, key should be :
//          key <= i
// otherwise, in max-heap, key >= i
// replace the formal one, ajust it from bottom to top
// to keep the requirement of min/max-heap 
//
HEAP_DECREASE_KEY(A, i, key)
{ 
    if (key > A[i]) 
        error: key is greater than current key

    A[i] = key

    while (i > 1 && A[PARENT(i)] > A[i]) {
        exchange A[i] with A[PARENT(i)]
        i = PARENT(i)
    }
}

时间复杂度也是 : O( lg n )


MIN_HEAP_INSERT 能够实现 INSERT 操作。
它的输入是要被插入到最小堆 A 中的新元素的关键字, MIN_HEAP_INSERT 首先增加一个关键字为(正无穷大)的叶子节点来扩展最小堆,(如果是给最大堆插入,就增加一个负无穷大的叶子节点)
然后调用 HEAP_DECREASE_KEY 为新节点设置对应的关键字,同时调整后保持最小堆的性质

时间复杂度是: O( lg n )

// key is the new element to insert in A
// we could add a new element at the tail
// initialze it with infinity(negative infinity) of min(max) heap  
// change the key of it
//
MIN_HEAP_INSERT(A, key)
{
    A.heap_size = A.heap_size + 1
    A[A.heap_size] = infinity in min-heap(or negative infinity in max-heap)

    HEAP_DECREASE_KEY(A, A.heap_size, key)
}

ADDITION 补充:

算法导论习题 6.5-8:

HEAP_DELETE( A, i ) 操作能够将节点 i 从堆 A 之中删除。对于一个包含 n 个元素的堆,设计一个能够在 O(lg n) 时间内完成的操作:


分析:

// design the operation delete(A, i)
// to delete the exact element i (means A[i]) in heap A
//
// time requirement should be O( log(n) )
//
HEAP_DELETE(A, i)
{
    if (i > A.heap_size)
        error: i is out of range

    key = A[A.heap_size]
    A.heap_size = A.heap_size - 1

    if (key < A[i]) {
        // min-heap should decrease, decrease will adjust from
        // bottom to top
        // max-heap should increase here (key > A[i])

        HEAP_DECREASE_KEY(A, i, key) //往上调整
    }
    else {
        //key >= A[i]

        A[i] = key
        MIN_HEAPIFY(A, i)        //往下调整
    }
}

算法导论习题 6.5-9:

请设计一个时间复杂度为 O(n * lg k)的算法,它能够将 k 个有序链表合并为一个有序链表,这里 n 是指所有输入链表包含的总的元素的个数(提示:用最小堆来完成 k 路合并)


分析:
我们利用最小堆的性质和操作,k个已经有序的链表,我们将每个链表的头元素放到最小堆中进行维护,然后每次选择最小的元素出来,根据所选节点所在的链表是否为空进行下一步操作即可。
这样,选择 n 个元素,每次选择可能导致 O(lg n)最小堆性质调整的时间。符合条件了。

实现:

step 1: 取每个链表的首元素,构造为一个含有 k 个元素的最小堆 (看原来链表的排序方式决定)
step 2: 若堆不为空,将根元素记录到已经排序的结果之中(新的结果链表)
step 3: 判断根节点所在的链表,若链表为空则goto step 4,否则goto step 5
step 4: 删除堆的根节点,调整之后 goto step 2
step 5: 将根节点替换为原根节点所在链表的下一个元素,调整堆,goto step 2

七、实现

伪代码实现:

#
# preparation
#

PARENT(i)
    return (int)(i/2)
#define PARENT(i) (i)>>2

LEFT(i)
    return (i * 2)
#define LEFT(i) (i)<<2

RIGHT(i)
    return (i * 2) + 1
#define RIGHT(i) ((i)<<2)+1


#
# operation
#

// suppose that the left & right children are already meet min-heap
// modify the location of i inorder to meet the requirement
//
MIN_HEAPIFY(A, i)
{
    left = LEFT(i) 
    right = RIGHT(i)

    if (left < A.heap_size && A[left] < A[i])
        minimal = left
    else
        minimal = i

    if (right < A.heap_size && A[right] < A[i])
        minimal = right

    if (minimal != i) {
        exchange A[i] with A[minimal]

        MIN_HEAPIFY(A, minimal)     //recurse
    }
}

//
//
MIN_HEAPIFY_LOOP(A, i)
{
    left = LEFT(i)
    right = RIGHT(i)

    if (left < A.heap_size && A[left] < A[i])
        minimal = left
    else
        minimal = i

    if (right < A.heap_size && A[right] < A[i])
        minimal = right

    while (minimal != i) {      // loop instead of recurse
        exchange A[i] with A[minimal]

        i = minimal
        l = LEFT(i)
        r = RIGHT(i)
        if (l < A.heap_size && A[l] < A[i])
            minimal = l
        else
            minimal = i

        if (r < A.heap_size && A[r] < A[i])
            minimal = r
    }
}


// A is the input data array
// from n/2 , bottom to top
//
BUILD_MIN_HEAP(A)
{
    A.heap_size = A.length
    for i = (A.length / 2) to 1
        MAX_HEAPIFY(A, i) 
}


// as a result, array A will maintain the sort result
// time requirement : O(n * log(n))
//
HEAPSORT(A)
{
    BUILD_MIN_HEAP(A);

    for i = A.length downto 2
    {
        exchange A[1] with A[i]     //A[1] store the minimal/largest
        A.heap_size = A.heap_size - 1
        MIN_HEAPIFY(A, 1)
    }
}



#
# priority queue: based on minimal-heap
#


// get the minimal element of the array A
//
HEAP_MINIMAL(A)
{
    return A[1]
}


// get the minimal, delete it from the origin data set
//
HEAP_EXTRACT_MIN(A)
{
    if (A.heap_size < 1)
        error: heap underflow

    min = A[1]
    A[1] = A[A.heap_size]
    A.heap_size = A.heap_size - 1

    MIN_HEAPIFY(A, 1)       // adjust

    return min
}


// in minimal-heap, key should be :
//          key <= i
// otherwise, in max-heap, key >= i
// replace the formal one, ajust it from bottom to top
// to keep the requirement of min/max-heap 
//
HEAP_DECREASE_KEY(A, i, key)
{ 
    if (key > A[i]) 
        error: key is greater than current key

    A[i] = key

    while (i > 1 && A[PARENT(i)] > A[i]) {
        exchange A[i] with A[PARENT(i)]
        i = PARENT(i)
    }
}


// key is the new element to insert in A
// we could add a new element at the tail
// initialze it with infinity(negative infinity) of min(max) heap  
// change the key of it
//
MIN_HEAP_INSERT(A, key)
{
    A.heap_size = A.heap_size + 1
    A[A.heap_size] = infinity in min-heap(or negative infinity in max-heap)

    HEAP_DECREASE_KEY(A, A.heap_size, key)
}


// design the operation delete(A, i)
// to delete the exact element i (means A[i]) in heap A
//
// time requirement should be O( log(n) )
//
HEAP_DELETE(A, i)
{
    if (i > A.heap_size)
        error: i is out of range

    key = A[A.heap_size]
    A.heap_size = A.heap_size - 1

    if (key < A[i]) {
        // min-heap should decrease, decrease will adjust from
        // bottom to top
        // max-heap should increase here (key > A[i])

        HEAP_DECREASE_KEY(A, i, key) 
    }
    else {
        //key >= A[i]

        A[i] = key
        MIN_HEAPIFY(A, i)
    }
}

min_heap.h

#include <iostream>

// preparation
//

#define PARENT(i) (i/2)
#define LEFT(i) (i*2)
#define RIGHT(i) (i*2)+1

//preparation end
//

#define DTYPE int
#define MAX 50

#define NEGATIVE_INFINITY -999999
#define INFINITY 999999


class Heap {
public:
    Heap();
    virtual ~Heap();

    bool Initialize(DTYPE *source, const int len);

    /*
     * after sort
     * max-heap:  A: 1 2 3 4 ...
     * min-heap:  A: 5 4 3 2 ...
     */
    void HeapSort(); 

    /*
     *  basement
     */
    void Min_Heapify(int i); 
    void Min_Heapify_Loop(int i);

    void Build_Min_Heap();      // call this after call Initialize()

    void exchange(const int a, const int b);

    void print_A();

    /*
     * priority-queue operation based on min-heap
     */
    DTYPE Heap_Minimal();       //get minimal
    DTYPE Heap_Extract_Min();   //get min, delete it

    void Heap_Decrease_Key(int i, DTYPE key);
    void Min_Heap_Insert(DTYPE key);
    bool Heap_Delete(int i);

private:
    DTYPE *A;

    int heap_size;
    int length;
};


min_heap.cc

#include "./min_heap.h"


Heap::Heap()
{
    A = new DTYPE[MAX + 1];
    heap_size = 0;
    length = 0;
}
Heap::~Heap()
{
    delete []A;
    heap_size = 0;
    length = 0;
}


bool Heap::Initialize(DTYPE *source, const int len)
{
    /*
    if (len > this->length) {
        std::cerr<<"source is too long.."<<std::endl;
        return false; 
    }
    */
    this->length = len;

    for (int i = 1; i <= len; i++)
        A[i] = source[i];

    this->heap_size = len;

    return true;
}


// time requirement : O(n * log(n))
//
// should invoke Initialize() before this.
//
// after this, A[] will be sorted
// heap_size will equal to 0
// length is also the length
//
void Heap::HeapSort()
{
    Build_Min_Heap(); 

    int len = this->length;
    for (int i = len; i >= 2; i--) {
        exchange(i, 1);

        this->heap_size--;
        Min_Heapify(1);
    }
}


/*
 * basement
 */
void Heap::Min_Heapify(int i)
{

    int left = LEFT(i);
    int right = RIGHT(i);

    std::cout<<"i="<<i<<", left="<<left<<", right="<<right<<std::endl;///

    int minimal = i;

    if (left <= this->heap_size && A[left] < A[minimal])
        minimal = left;
    if (right <= this->heap_size && A[right] < A[minimal])
        minimal = right;

    if (minimal != i) {
        std::cout<<"before exchange: A[i]="<<A[i]<<", A[minimal]="<<A[minimal]<<std::endl;
        exchange(i, minimal);
        std::cout<<" exchange: A[i]="<<A[i]<<", A[minimal]="<<A[minimal]<<std::endl;

        Min_Heapify(minimal);
    }
}

void Heap::Min_Heapify_Loop(int i)
{
    int j = i;
    int left = LEFT(i);
    int right = RIGHT(i);

    int minimal = j;
    if (left <= this->heap_size && A[left] < A[minimal])
        minimal = left;
    if (right <= this->heap_size && A[right] < A[minimal])
        minimal = right;

    while (minimal != j) {
        exchange(minimal, j);

        j = minimal;
        int l = LEFT(j);
        int r = RIGHT(j);
        if (l <= this->heap_size && A[l] < A[j])
            minimal = l;
        if (r <= this->heap_size && A[r] < A[minimal])
            minimal = r;
    }
}

// call this after call Initialize()
// handle the input data set A[]
// from A.length/2 down to node 
// min_heapify() each node
//
void Heap::Build_Min_Heap()
{
    this->heap_size = this->length;

    //std::cout<<"heap_size = "<<heap_size<<", length = "<<length<<std::endl;

    int loop_len = static_cast<int>(this->length / 2);

    //std::cout<<"loop len = "<<loop_len<<std::endl;

    for (int i = loop_len; i >= 1; i--) {
        std::cout<<"i = "<<i<<std::endl; //

        Min_Heapify_Loop(i);
    }
}

// a, b is the superscript of A
// exchange the element A[a] with A[b]
void Heap::exchange(const int a, const int b)
{
    DTYPE temp = A[a];
    A[a] = A[b];
    A[b] = temp;
}

void Heap::print_A()
{
    for (int i = 1; i <= this->length; i++) 
        std::cout<<A[i]<<" ";
    std::cout<<std::endl;
}

/*
 *  priority-queue : 
 *  operation based on minimal-heap
 */

//get minimal
DTYPE Heap::Heap_Minimal()
{
    if (this->heap_size < 1) {
        std::cerr<<"heap is empty.."<<std::endl; 
        return -1;
    }
    return A[1];
}

//get min, delete it
DTYPE Heap::Heap_Extract_Min()
{
    if (this->heap_size < 1) {
        std::cerr<<"heap is empty.."<<std::endl; 
        return -1;
    }

    int min = A[1];
    A[1] = A[this->heap_size];
    this->heap_size = this->heap_size - 1;
    Min_Heapify(1);

    return min;
}

// in minimal-heap, key should : 
//          key <= i
//
// min-heap: decrease key
// max-heap: increase key
//
void Heap::Heap_Decrease_Key(int i, DTYPE key)
{
    if (key > A[i]) {
        std::cerr<<"key > A["<<i<<"].."<<std::endl;
        return ;
    }

    A[i] = key;
    while (i > 1 && A[PARENT(i)] > A[i]) {
        exchange(i, PARENT(i));

        i = PARENT(i);
    }
}

// insert at the tail
// min-heap: initialize with INFINITY
// max-heap: initialize with NEGATIVE_INFINITY
//
void Heap::Min_Heap_Insert(DTYPE key)
{
    this->heap_size = this->heap_size + 1;
    A[this->heap_size] = INFINITY;

    Heap_Decrease_Key(this->heap_size, key);
}

// design the function delete(A, i)
// delete the exact element i (means A[i]) in heap A
//
// time requirement : O( log(n) )
//
bool Heap::Heap_Delete(int i)
{
    if (i > this->heap_size) {
        std::cerr<<"i is out of range.."<<std::endl; 
        return false;
    }

    int key = A[this->heap_size];
    this->heap_size = this->heap_size - 1;

    //based on min-heap
    if (key < A[i]) {
        // max-heap, this could be increase_key()

        Heap_Decrease_Key(i,  key);//adjust from bottom to top
    } else {
        A[i] = key;
        Min_Heapify(i);//adjust from top to bottom
    }
    return true;
}

test.cc

#include "./min_heap.h"


int main()
{
    //input data
    int N;
    scanf("%d", &N);

    if (N > MAX) {
        std::cout<<"N > MAX.."<<std::endl; 
        return -1;
    }

    DTYPE source[MAX + 1];

    for (int i = 1; i <= N; i++) {
        int temp; 
        scanf("%d", &temp);
        source[i] = temp;
    }
    //end input


    Heap _obj = Heap();

    if (_obj.Initialize(source, N) == false) {
        std::cout<<"Initialize error.."<<std::endl; 
        return -1;
    }

    _obj.Build_Min_Heap();

    _obj.print_A();


    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值