堆的概念以及实现堆

来源:互联网 发布:淘宝大马士革刀真假 编辑:程序博客网 时间:2024/06/05 03:19

  普通队列就是遵循时间原则,先进先出,后进后出的一种队列结构;而优先队列则是按照优先级来决定出队的次序,与何时进入队列无关,实际生活中有很多优先队列的例子:医院候诊病人中,急诊病人优先看病,而不是按照病人到达医院的先后次序来,这就是一个优先队列的例子。

  优先队列适合应用于动态场景中,比如游戏中自动作战,当存在多个敌人时,不是按照敌人出现的顺序进行攻击,而是根据某个优先级(比如先攻击血量最大的)进行攻击,为什么说是动态的?是因为不是在攻击完血量最大的敌人之后就接着攻击剩余的敌人,实际上还会有其他的敌人陆续出现,这时候再在所有敌人中选出优先级最高的进行攻击。

  实际上,除了在动态数据的处理上优先队列很有优势,优先队列在处理静态数据时有时也是很有优势的。

  优先队列的实现方式有几种:



  当总共需要进行N次时,采用普通数组和顺序数组实现的优先队列的时间复杂度最差为O(n^2),而采用堆实现的优先队列时间复杂度为O(Nlogn)。

  接下来介绍堆这种数据结构。

  什么是堆?经典的是二叉堆,对应的就是一棵二叉树,二叉堆又分为最大堆和最小堆。最大堆:堆中每个节点的值都不大于其双亲节点的值,根结点为最大值;最小堆:堆中每个节点的值都不小于其双亲节点的值,根结点为最小值。二叉堆总是一棵完全二叉树。

  二叉堆的经典存储方式是使用数组来存储。



  根据这种存储方式,对数组中下标为 i 的元素,其双亲结点就对应数组中下标为 i/2 的元素,其左孩子为数组中下标为 2i 的元素,其右孩子为数组中下标为 2i+1 的元素。

  接下来以最大堆为例,用C++实现最大堆:

#include <iostream>#include <cassert>#include <algorithm>#include <cstdlib>#include <ctime>using namespace std;template<typename Item>class MaxHeap{private:    Item *data;    int count;    int capacity;    void shiftUp(int k)    {        //凡是涉及到数组下标的,都要限定它不出界        while(k > 1 && data[k] > data[k / 2])        {            swap(data[k], data[k / 2]);            k /= 2;        }    }    void shiftDown(int k)    {        //当k有孩子的情况下开始shiftDown操作        while( 2*k <= count )        {            int j = 2*k;  //j就是要和data[k]交换的元素的位置下标            //判断是否有右孩子            if( j+1 <= count && data[j+1] > data[j] )            {                j = j + 1;            }            if(data[k] >= data[j])            {                break;            }            swap(data[k], data[j]);            k = j;        }    }public:    MaxHeap(int capacity)    {        data = new Item[capacity + 1];  //从1号单元开始存放        count = 0;        this->capacity = capacity;    }    ~MaxHeap()    {        delete[] data;    }    //返回堆现在的大小    int size() const    {        return count;    }    //判断堆是否为空堆    bool isEmpty() const    {        return count == 0;    }    //向堆中插入一个元素    void insert(Item item)    {        //判断count+1是否出界        assert(count + 1 <= capacity);        data[count + 1] = item;        count++;        //从count位置处开始向上调整堆        shiftUp(count);  //这个函数不需要被用户调用,所以设为私有    }    //取出堆中的一个元素(只能取出根结点)    Item extractMax()    {        //首先判断堆是否为空        assert(count > 0);        Item ret = data[1];        data[1] = data[count];        count--;        shiftDown(1);        return ret;    }    //打印堆中内容    void printData() const    {        for(int i = 1; i <= count; i++)        {            cout << data[i] << " ";        }        cout << endl;    }};int main(){    MaxHeap<int> maxheap = MaxHeap<int>(100);  //利用构造函数初始化    cout << maxheap.size() << endl;    //随机插入几个元素    srand(time(0));    while(maxheap.size() < 10)    {        int item = rand() % 100 + 1;        maxheap.insert(item);    }    cout << maxheap.size() << endl;    maxheap.printData();    //取出堆中元素(实际上就是将堆中元素从大到小排序的过程)    while(!maxheap.isEmpty())    {        cout << maxheap.extractMax() << " ";    }    cout << endl;    return 0;}
  我们将最大堆声明为一个类,因为堆的大小是由用户定义的,所以使用指针 Item *data 来动态分配内存(使用构造函数 MaxHeap(int capacity)来实现),在构造函数中使用 new[] 动态分配内存后,相应的就需要在析构函数中 delete[] 内存。

  接下来分析向堆中插入元素的过程:只能在数组的最后一个位置插入元素,此时的完全二叉树不满足堆的定义,所以需要进行向上调整堆的过程,若新插入节点值大于其双亲节点的值,则交换两者位置,重复此步骤直至根结点(由于用户只需要调用插入元素的函数,不需要调用向上调整的函数,所以将shiftUp(int k)定义在私有成员中)。

  接下来分析取出堆中元素的过程:只能取出根节点的值(最大值),此时根节点的值用堆中最后一个元素的值代替,此时新的完全二叉树不满足堆的定义,所以需要向下调整堆。若根节点的值小于其左右孩子中的较大值,则交换两者位置,重复此步骤,直到其没有孩子为止(由于用户只需要调用取出元素的函数,不需要调用向下调整的函数,所以将shiftDown(int k)定义在私有成员中)。

  在main函数中,最后的while(!maxheap.isEmpty())循环按从大到小的顺序打印出堆中所有元素,实际上就是一个排序的过程,打印结果如下:



  在上述代码中,需要多次进行swap操作,这是一项耗时的操作,最好使用赋值操作来取代之:即先找到要交换的位置,然后赋值。优化后的 shiftUp() 和 shiftDown() 代码如下:

    void shiftUp(int k)    {        //凡是涉及到数组下标的,都要限定它不出界        //优化:用赋值取代交换        Item item = data[k];        while( k>1 && data[k/2] < item )        {            data[k] = data[k/2];            k /= 2;        }        data[k] = item;    }    void shiftDown(int k)    {        //当k有孩子的情况下开始shiftDown操作        //优化:用赋值取代交换        Item item = data[k];        while( 2*k <= count )        {            int j = 2*k;            if(j + 1 <= count && data[j + 1] > data[j])            {                j = j + 1;            }            if(data[j] <= item)            {                break;            }            data[k] = data[j];            k = j;        }        data[k] = item;    }

  到这里,如何实现最大堆就讲完了,下一节将实现堆排序以及其优化。




  

原创粉丝点击