优先级队列——二叉堆的原理及实现

前言

在学习了队列和栈之后,我们知道了两种进出方式,队列:先进先出,栈:后进先出,那如果我们想让一堆数据按照其优先级顺序进出呢?所以就引入了一种新的数据结构——二叉堆。

二叉堆

二叉堆是一种基于树的优先级队列,为什么要基于树呢,因为树状结构能使其具有对数级的时间性能。

来张图便于大家理解:

下面我们都以最小堆为例进行讲解。

二叉堆的性质:

结构性:二叉堆是一棵完全二叉树,这保证其有很好的性能,而且可以拿数组来实现,父节点i,子节点2 * i 与2 * i + 1。

有序性:二叉堆是关于元素的优先级有序的,即父节点的优先级总是高于它的两个子节点的优先级,堆的根节点的优先级总是所有                 元素中最大的。

基本实现

存储实现:

二叉堆是一棵完全二叉树,所以可以使用数组来进行存储。在记录时记下当前数组中的元素数目,和数组的最大规模。

class priorityQueue{
    
private:
    int *arr;
    int curSize;
    int maxSize;

}

enQueue操作:

优先级队列——二叉堆的原理及实现_第1张图片比如说我们想在上面树中插入x = 15,我们就在树的最末端插入一个空穴,然后我们发现这样破坏了二叉堆的有序性,所以我们就让空穴上冒,让x与空穴的父节点比较,x < 31,那样就交换空穴与其父节点,继续比较,x < 21,继续交换,然后发现x > 13,这时我们将x放入空穴,这时二叉堆又恢复了有序性。

这里还要注意在插入前要判断数组会不会炸,如果会炸要doubleSpace。

    void doubleSpace(){
        int i;
        int *tmp = arr;
        arr = new int[maxSize * 2];
        maxSize = maxSize * 2;
        for(i = 1; i < maxSize; i++){
            arr[i] = tmp[i];
        }
        delete []tmp;
    }
    void enQueue(int x){
        if(curSize == maxSize - 1){
            doubleSpace();
        }
        curSize++;
        arr[curSize] = x;
        //push up
        int hole = curSize;
        while(true){
            if(hole == 1 || arr[hole / 2] <= x){
                arr[hole] = x;
                break;
            }
            
            arr[hole] = arr[hole / 2];
            hole = hole / 2;
        }
    }

deQueue操作:

二叉堆的deQueue操作就是推出优先级最高的元素,那不就根节点空了吗,怎样恢复二叉堆的有序性呢?

优先级队列——二叉堆的原理及实现_第2张图片

如图,我们先推出了13,然后将堆最末端的元素32补到根节点,然后删去末端元素,之后对根节点进行下推操作。我们找到根节点的左右子节点中的最小值16,32大于16,然后就16上冒到根节点的位置,16的位置变为空穴,之后32再与空穴的左右子节点中的最小值进行比较,发现32大于19,那么19上冒到空穴的位置,19的位置变为空穴。然后发现空穴再没有子节点了,那就将32放入空穴,大功告成,二叉堆有恢复了有序。

    void pDown(int id){
        int x = arr[id];
        int hole = id;
        int child = hole * 2;
        if(arr[child] > arr[child + 1]){
            child++;
        }

        while(true){
            if(hole * 2 > curSize || x < arr[child]){
                arr[hole] = x;
                break;
            }
            arr[hole] = arr[child];
            hole = child;
            child = hole * 2;
            if(child !=  curSize && arr[child] > arr[child + 1]){
                child++;
            }
        }
    }
    
    int deQueue(){
        int minn = arr[1];
        arr[1] = arr[curSize];
        curSize--;
        pDown(1);
        return minn;
    }

buildTree操作:

我们学习了enQueue操作,为什么还要学建树呢,直接不断的enQueue不就行了。行是行,但这样enQueue一个元素是O(logn),总时间复杂度达到了O(nlogn),是不是有种不祥的预感(这怕是要超时啊),那怎么优化呢?

我们可以直接将数据建成一棵二叉树,当然这时的二叉树乱的一匹,我们想恢复其有序性,只要从树的前一半节点进行下推操作即可,这样的时间复杂度为O(n),其实这里我也很懵逼,怎么就O(n)了,此处转载知乎大神的讲解,真的niuB:

优先级队列——二叉堆的原理及实现_第3张图片

    void buildHeap(){
        int i;
        curSize = N;
        for(i = 1; i <= curSize; i++){
            arr[i] = item[i - 1];
        }
        
        for(i = curSize / 2; i >= 1; i--){
            pDown(i);
        }
    }

完整代码:

#include 

using namespace std;

const int N = 1000;
int item[N];

class priorityQueue{
private:
    int *arr;
    int curSize;
    int maxSize;

public:
    priorityQueue(int size = 1000){
        arr = new int[size];
        maxSize = size;
        curSize = 0;
    }
    
    ~priorityQueue(){
        delete []arr;
    }

    void doubleSpace(){
        int i;
        int *tmp = arr;
        arr = new int[maxSize * 2];
        maxSize = maxSize * 2;
        for(i = 1; i < maxSize; i++){
            arr[i] = tmp[i];
        }
        delete []tmp;
    }
    void enQueue(int x){
        if(curSize == maxSize - 1){
            doubleSpace();
        }
        curSize++;
        arr[curSize] = x;
        //push up
        int hole = curSize;
        while(true){
            if(hole == 1 || arr[hole / 2] <= x){
                arr[hole] = x;
                break;
            }
            
            arr[hole] = arr[hole / 2];
            hole = hole / 2;
        }
    }
    
    int getHead(){
        return arr[1];
    }
    
    void pDown(int id){
        int x = arr[id];
        int hole = id;
        int child = hole * 2;
        if(arr[child] > arr[child + 1]){
            child++;
        }

        while(true){
            if(hole * 2 > curSize || x < arr[child]){
                arr[hole] = x;
                break;
            }
            arr[hole] = arr[child];
            hole = child;
            child = hole * 2;
            if(child !=  curSize && arr[child] > arr[child + 1]){
                child++;
            }
        }
    }
    
    int deQueue(){
        int minn = arr[1];
        arr[1] = arr[curSize];
        curSize--;
        pDown(1);
        return minn;
    }
    
    void buildHeap(){
        int i;
        curSize = N;
        for(i = 1; i <= curSize; i++){
            arr[i] = item[i - 1];
        }
        
        for(i = curSize / 2; i >= 1; i--){
            pDown(i);
        }
    }
    
    void printHeap(){
        int i;
        for(i = 1; i <= curSize; i++){
            cout << arr[i] << " ";
        }
        cout << endl;
    }
};

总结

二叉堆是一种常用于优先级队列的数据结构,当你想要快速的得到优先级最高的元素时,二叉堆可以很好的满足这一需求,因为二叉堆提取优先级最高的元素是O(1)的,在其他方面,二叉堆也有着对数级别的复杂度,性能很好。主要应用有A*寻路、Dijkstra's算法(计算最短路径)等。

你可能感兴趣的:(优先级队列——二叉堆的原理及实现)