二叉堆

       堆是一个完全二叉树,因为完全二叉树有特殊的规律,其可以从上到下从左至右逐层排列到一个数组中,所以可以用线性空间表示堆而不必使用链表。如下图的堆可以用下面的数组表示:

完全二叉树(堆)

完全二叉树的数组表示

        对一个完全二叉树,具有几个基本特征。完全二叉树中的任意次序为i的结点,其左孩子在2i位置上,右孩子在2i+1位置上,因此不需要链表遍历该树很简单,在大部分计算机上运行的非常的快。在这种实现方式中,堆的容量需要实现估计,但是在需要的情况下我们可以重新调整。如上图所示,堆的总容量是13,0位置保留他用。因此,堆结构可用一个线性空间和一个代表堆大小的数据成员组成,用C++描述如下:

template<typename T>
class Heap{
public:
    explicit Heap(size_t capacity = 100); //构造函数
    explicit Heap(const std::vector<T> &); //读入数据系列构造堆
    
    bool isEmpty() const; //判断堆空
    T &top() const; //返回堆的顶点

    void push(const T &); //插入元素
    void pop(); //删除元素
    void pop(T &); //删除元素并将其保存起来
    void clear(); //清空堆
private:
    int currSize;  //堆的实际大小
    std::vector<T> heapArr;  //堆的线性空间
    void buildHeap(); //构造堆
    void filterDown(int /* begin position*/); //元素上滤
    void filterUp(int /* begin position*/); //元素下滤
};

        堆根据性质又可以分为大根堆(大顶堆、最大堆)和小根对(小顶堆、最小堆),前者根结点的键值是所有堆结点键值中最大者,后者根结点的键值是所有堆结点键值中最小者。本人以小根堆为主要实现。

        1、push

        为将一个元素N插入到堆中,在空闲位置,即最后一个有效元素的下一个位置创建一个空穴,如果N的插入不破坏堆的性质,则插入完成,否则把该空穴的父节点上的元素移动到空穴中,这样一来,空闲就朝着根的方向上了一层。重复该过程,知道该N能放入堆中而不影响堆的性质为止。如下图所示,为了插入14,在下一个空闲位置创建一个空穴,由于14的插入破坏了小根堆的性质,因此将31移入到空穴中,如此重复,直到找出放置14的位置。


尝试插入14,创建一个空穴,破坏堆的性质,空穴上冒


将14上滤到合适位置并插入

push实现代码:

template<typename T>
void push(const T &val){
    if(currSize == heapArr.size() - 1)//初始化堆的时候多加了1,此处应该减去1
        heapArr.resize(2*currSize);
    int pos = ++currSize; //在最后一个有效元素的下一个位置插入空元素heapArr[pos]
    //此处pos > 1,因为heapArr的有效存储位置是从1开始的。pos的父节点位置为pos/2
	for(;pos > 1 && val < heapArr[pos/2];pos /= 2){
        heapArr[pos] = heapArr[pos/2];//如果插入的节点值小于父节点,则将空元素的父节点移到空穴heapArr[pos]
    }
    heapArr[pos] = val;
}

如果插入的元素是新的最小值,则它将一直被推向顶端,直到某一时刻pos为1,pos为0处不放置元素。

        2、pop

        pop以类似push的方式处理,找出最小元素是容易,即最小元素就在根位置,但是删除则较为麻烦。当删除了堆根时,此处成了一个空穴,因此堆中的最后一个元素需要移动到堆中的某个位置。如果最后一个元素可以放入空穴中,那么删除完成。我们假设将最后一个元素的值移入到空穴中,如果他比左右孩子都小,那么久万事大吉了,否则,将较小的孩子移动到空穴中,那么空穴就下移了一层。重复该步骤,知道找打适合的位置,这个过程一般称为下滤。如下图所示,删除根13后,31不能放入空穴中,因为这会破坏堆的性质。于是把叫小的儿子14移入空穴,这样空穴就下沉了一层。接着,31依然大于19,继续重复,直到空穴找到合适的位置把31移入空穴中。

在根处建立空穴

空穴下移两层

空穴移到合适位置并将31置入空穴中


pop的实现代码:

template<typename T>
void pop(){
    if(isEmpty()) throw std::exception();
    heapArr[1] = heapArr[currSize--];//删除第一个元素的值使其成为空穴
    
    filterDown(1);//从第一个节点开始下滤
}
void filterDown(int pos){
    T tmp = heapArr[pos];
    int targetChild;
    for(;2*pos <= currSize;pos = targetChild){
        targetChild = 2 * pos;
	//如果左孩子大于右孩子,则选择值较小的右孩子作为目标孩子
        if(heapArr[2*pos] > heapArr[2*pos+1]){
            ++targetChild;
        }
	//如果左右孩子其中一个比其父节小,则将较小的孩子往上移动一层
        if(heapArr[targetChild] < tmp)
            heapArr[pos] = heapArr[targetChild];
        else
            break;
    }
    heapArr[pos] = tmp;
}

值得注意的是,当堆存在偶数个结点是,肯定有一个结点只有一个子结点,因此有必要增加filterDown中第7行的测试。


小根堆的C++实现:

/*
 * File: MinHeap.hpp
 */

#ifndef MINHEAP_H
#define MINHEAP_H
#include <vector>
template<typename T>
class MinHeap{
public:
    explicit MinHeap(size_t capacity = 100); //构造函数
    explicit MinHeap(const std::vector<T> &); //读入数据系列构造堆
    
    bool isEmpty() const; //判断堆空
    const T &top() const; //返回堆的顶点

    void push(const T &); //插入元素
    void pop(); //删除元素
    void pop(T &); //删除元素并将其保存起来
    void clear(); //清空堆
private:
    int currSize;  //堆的实际大小
    std::vector<T> heapArr;  //堆的线性空间
    void buildHeap(); //构造对
    void filterDown(int /* begin position*/); //元素上溯
};

/*
 *小根堆根据完全二叉树实现,完全二叉树的节点按照从上到下从左到右的顺序 
 *存储在线性的数组heapArr中,数组第一个元素保留,所以初始化容量要+1以容
 *纳实际的容量。堆的初始大小currSize为0。
 */
template<typename T>
MinHeap<T>::MinHeap(size_t capacity):heapArr(capacity+1),currSize(0){}

/*
 *从一个系列中的数据初始化堆
 */
template<typename T>
MinHeap<T>::MinHeap(const std::vector<T> & items):heapArr(items.size()+1),currSize(items.size()){
    int i;
    for(i = 1;i <= items.size();++i)
        heapArr[i] = items[i];
    buildHeap();
}

template<typename T>
bool MinHeap<T>::isEmpty() const{ return currSize == 0;}

template<typename T>
const T &MinHeap<T>::top() const{
    if(isEmpty())
        throw std::exception();
    return heapArr[1];
}

template<typename T>
void MinHeap<T>::push(const T &val){
    if(currSize == heapArr.size() - 1)
        heapArr.resize(2*currSize);
    int pos = ++currSize; 
    for(;pos > 1 && val < heapArr[pos/2];pos /= 2){ 
        heapArr[pos] = heapArr[pos/2]; 
    }
    heapArr[pos] = val;
}

template<typename T>
void MinHeap<T>::pop(){
    if(isEmpty()) throw std::exception();
    heapArr[1] = heapArr[currSize--];//删除第一个元素的值使其成为空穴
    
    filterDown(1);//从第一个节点开始下滤
}

template<typename T>
void MinHeap<T>::pop(T &minItem){//删除最小的顶点并将其值保存到minItem中
    if(isEmpty()) throw std::exception();
    minItem = heapArr[1];
    heapArr[1] = heapArr[currSize--];
    filterDown(1);
}

template<typename T>
void MinHeap<T>::clear(){currSize = 0;}

/*
 *依次对每个非叶子节点下滤,构造小顶堆
 */
template<typename T>
void MinHeap<T>::buildHeap(){
    int i;
    for(i = currSize/2;i >= 1;--i)
        filterDown(i);
}

template<typename T>
void MinHeap<T>::filterDown(int pos){
    T tmp = heapArr[pos];
    int targetChild;
    for(;2*pos <= currSize;pos = targetChild){
        targetChild = 2 * pos;
        if(heapArr[2*pos] > heapArr[2*pos+1]){
            ++targetChild;
        }
        if(heapArr[targetChild] < tmp)
            heapArr[pos] = heapArr[targetChild];
        else
            break;
    }
    heapArr[pos] = tmp;
}

#endif

/*
 * File: MinHeap.cpp
 */
#include <iostream>
#include <iterator>
#include <algorithm>
#include <ctime>
#include <cstdlib>
#include "MinHeap.hpp"

using namespace std;

int main(){
    srand(time(0));
    int i;
    MinHeap<int> heap1;
    for(i = 0;i < 10;i++)
        heap1.push(rand()%100+1);
    while(!heap1.isEmpty()){
        cout<<heap1.top()<<" ";
        heap1.pop();
    }
    cout<<endl;

    return 0;
}

测试结果:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值