堆的实现以及优先级队列

本文介绍了堆的概念,包括大顶堆和小顶堆,并详细阐述了堆的实现过程,包括如何调整堆以保持性质。接着讨论了在堆中插入和删除元素的操作,以及如何通过代码实现这些操作。此外,文章提到了C++中的优先级队列,它是堆的一个封装,提供常数时间的最大元素查找和对数时间的插入与删除。
摘要由CSDN通过智能技术生成

堆的概念

堆是将一组数据按照完全二叉树的存储顺序,将数据存储在一个一维数组中的结构。
堆有两种结构,一种称为大顶堆,一种称为小顶堆,如下图。
小顶堆:任意结点的值均小于等于它的左右孩子,并且最小的值位于堆顶,即根节点处。
大顶堆:任意结点的值均大于等于它的左右孩子,并且最大的值位于堆顶,即根节点处。

这里写图片描述

既然是将一组数据按照树的结构存储在一维数组中,那么父子之间关系的建立就很重要了。
假设一个节点的下标为i。
1、当i = 0时,为根节点。
2、当i>=1时,父节点为(i - 1)/2

堆的实现

以下面这个例子来建堆,

这里写图片描述

int arr[10] = {12,23,54,2,65,45,92,47,204,31}
arr.size() = 10;

先看一下一次调整:
1、int i = (size - 2)/2 = 4找到数组中的4号下标。
65大于其孩子,满足大堆性质,所以不用交换。
这里写图片描述

2、i= i-1;然后用2和其孩子比较,2和204交换。交换之后满足大堆
这里写图片描述

3、54和其孩子比较,54和92交换。交换之后满足大堆性质
这里写图片描述

4、23和其孩子比较,23和204交换
这里写图片描述
交换完之后,它的子树却不满足了,所以还需调整它的子树。
这里写图片描述

5、12和204交换。
这里写图片描述
因为每一次调整都可能会影响到你的子树不满足,所以当你修改了一个节点后,一定要再去判断它的子树还是否满足,即需要向下调整

插入

在堆中插入一个元素,因为本身堆是建好的,所以满足性质,插入一个在树的最后,只需要顺着这条支路做一次调整就好了,不过这次不需要向下调整了,因为父节点肯定是大于子节点的。
这里写图片描述
如图,如果插入的节点大于它的父节点,发生交换是肯定不会影响其子树的,因为父节点本身就大于它的子节点,更何况交换后的节点是大于父节点的。所以,这里只需要一次简单的向上调整就可以完成。
这里写图片描述

删除
因为堆本身就是一种特殊的结构,所以一般对堆中的数据进行操作都是针对堆顶的元素,即针对最大值或最小值,比如哈夫曼树就用堆的结构来实现,因为每次要找出最小的两个数。
所以我们删除的时候,一般也是删除堆顶那个最小或最大的值,但是如果直接删掉堆顶,整个堆的结构被破坏了,必须重现建堆,这样无疑效率非常低,所以采用将堆中最后一个元素和堆顶元素进行替换,然后删除堆中最后一个元素。
这里写图片描述
这样进行替换删除后,以堆顶为根的树不满足堆的性质,只需要进行一次简单的向下调整即可。

下面是实现堆的代码,因为考虑到大小堆问题,所以使用了仿函数,这样只需多给定一个参数即可控制是大堆还是小堆。

下面给出完整代码:

#pragma once

#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;


template <class T>
class Less
{
public:
    bool operator()(const T& l, const T& r)
    {
        return l < r;
    }
};

template <class T>
class Greader
{
public:
    bool operator()(const T& l, const T& r)
    {
        return l > r;
    }
};

template <class T,class Compare = Greader<T>>
class Heap
{
public:
    Heap()
    {}

    Heap(const T a[], size_t n)
    {
        _heap.resize(n);
        for (size_t i = 0; i < n; ++i)
        {
            _heap[i] = a[i];
        }

        for (int root = (_heap.size()-2) >> 1; root >= 0; --root)
        {
            AdJustDown(root);
        }
    }

    void Push(const T& value)
    {
        _heap.push_back(value);

        if(_heap.size() > 1)
        AdJustUp();
    }

    void Pop()
    {
        assert(_heap.size() > 0);
        swap(_heap[0], _heap[_heap.size() - 1]);
        _heap.pop_back();
        if (_heap.size() > 0)//防止堆中只有一个节点,删除后为0
        AdJustDown(0);

    }

    const T& Top()//定义为const,防止堆顶被改破坏结构
    {
        return _heap[0];
    }

    size_t Size()
    {
        return _heap.size();
    }

    bool Empty()
    {
        return _heap.empty();
    }
private:
    void AdJustDown(int root)
    {
        int parent = root;
        int child = root * 2 + 1;
        while (child < _heap.size())
        {
            if ((child + 1) < _heap.size() && Compare()( _heap[child + 1] , _heap[child]))
                ++child;

            if (Compare()(_heap[child],_heap[parent]))
            {
                swap(_heap[parent] , _heap[child]);
                parent = child;
                child = child * 2 + 1;
            }
            else
            {
                break;
            }
        }
    }

    void AdJustUp()
    {
        int child = _heap.size() - 1;
        int parent = (child - 1) >> 1;

        while (parent >= 0)//chile > 0
        { 
            if (Compare()(_heap[child] , _heap[parent]))
            {
                swap(_heap[child], _heap[parent]);
                child = parent;
                parent = (child - 1) >> 1;
            }
            else
            {
                break;
            }
        }
    }
private:
    vector<T> _heap;
};


优先级队列

在C++的标准库中,提供了优先级队列这个容器,其实底层就是一个堆的结构,只不过将堆封装了一层而已。其实名字叫个优先级队列,但总觉得和队列是不沾边的。
priority_queue 是容器适配器,它提供常数时间的(默认)最大元素查找,对数代价的插入与释出。
用 priority_queue 工作类似管理某些随机访问容器中的堆,好处是不可能突然把堆非法化。
下面给出代码:

#pragma once
#include"Heap.h"

template<class T,class Compare=Greader<T>>
class Priority_Queue
{
public:
    Priority_Queue()
    {}

    ~Priority_Queue()
    {}

    const T& Top()const
    {
        return hp.Top();
    }

    bool Empty()
    {
        return hp.Empty();
    }

    size_t Size()
    {
        return hp.Size();
    }
    void Pop()
    {
        hp.Pop();//堆中已经对元素的个数进行了判断
    }

    void Push(const T& data)
    {
        hp.Push(data);
    }

    void Print_Queue()
    {
        while (!hp.Empty())
        {
            cout << hp.Top() << " ";
            hp.Pop();  
        }
        cout << endl;
    }
private:
    Heap<T,Compare> hp;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值