堆的介绍与应用

堆是一种特殊的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是指二叉堆。堆数据结构是一种数组对象,它可以被视为一棵完全二叉树结构。
堆结构的二叉存储分为2种形式,分别是最大堆和最小堆,最大堆就是每个父节点的值都大于或等于孩子节点的值,最小堆刚好与之相反,每个父节点的值都小于或等于孩子节点的值。
用以下代码实现一个堆,默认情况下是大堆的形式:

#include <iostream>
#include <assert.h>
#include <vector>
using namespace std;
//仿函数
template <typename T>
struct GREATER
{
    bool operator()(const T& x1, const T& x2)
    {
        return x1 > x2;
    }
};
template <typename T>
struct LESS
{
    bool operator()(const T& x1, const T& x2)
    {
        return x1 < x2;
    }
};

template<typename T,typename Compare=GREATER<T>>
class HEAP
{
public:
    HEAP(T* a, size_t sz)
    {
        for (size_t i = 0; i < sz; i++)
        {
            _a.push_back(a[i]);
        }
        //建堆,找到最后一个叶子节点的父节点
        for (int i = (sz - 2) / 2; i >= 0; --i)
            _AdjustDown(i);
    }
    HEAP()
    {}
    void Push(const T& x)
    {
        _a.push_back(x);
        _AdjustUp(_a.size()-1);
    }
    void Pop()
    {
        assert(!_a.empty());
        swap(_a[0],_a[_a.size()-1]);
        _a.pop_back();
        _AdjustDown(0);
    }
    T Top()
    {
        return _a[0];
    }
    bool Empty()
    {
        return _a.empty();
    }
    size_t Size()
    {
        return _a.size();
    }
private:
    //向下调整
    //考虑兄弟之间的关系
    void _AdjustDown(int parent)
    {
        int child = parent * 2 + 1;
        int size = _a.size();
        Compare com;
        while (child < size)
        {
            if (child + 1 < size&&com(_a[child + 1], _a[child]))
            {
                ++child;
            }
            if (com(_a[child], _a[parent]))
            {
                swap(_a[child], _a[parent]);
                parent = child;
                child = parent * 2 + 1;
            }
            else
            {
                break;
            }
        }
    }
    //向上调整
    void _AdjustUp(int child)
    {
        int parent = (child-1) / 2;
        Compare com;
        while (child>=0)
        {
            if (com(_a[child], _a[parent]))
            {
                swap(_a[child],_a[parent]);
                child = parent;
                parent = (child - 1) / 2;
            }
            else
            {
                break;
            }
        }
    }
private:
    vector<T> _a;
};
void Test()
{
    int a[] = { 2, 1, 3, 4, 7 };
    size_t size = sizeof(a) / sizeof(a[0]);
    HEAP<int,GREATER<int>>heap((int*)a,size);
    cout << heap.Top() << endl;

}

其中都是以最后一个叶子节点的父节点开始建堆,push的时候,是采用向上调整的方式,一直调整到根节点为止,来保持大堆或者小堆,pop的时候只能是pop掉最后一个节点,但我们想删除根节点。所以我们可以先将根节点与最后一个叶子节点交换,然后再进行向下调整,直到变为最大堆或者最小堆。

堆的应用:
1.TopK的问题,当要求寻找一堆数据中最大的前K个值时,我们往往会想到排序,在找出最大的前K个数据,这在数据N比较小的时候还行的通,但是数据N特别大,不能加载到内存中,我们就用堆来解决这个问题。
首先我们先取出前K个数据保存在数组a中,然后用这些数据建立小堆,a[0]就是K个数据中最小的,从第K+1个数据开始和a[0]进行比较,若其大于a[0],就替换a[0],从根结点开始进行向下调整,调整为新的最小堆,第K+2个元素在与此时的a[0]进行比较,如此反复,直到比较到最后一个数据为止。
代码如下:

//小堆
void AdjustDown(int* a, int n, int root)
{
    int parent = root;
    int child = parent * 2 + 1;
    while (child<n)
    {
        if (child + 1 < n&&a[child+1] < a[child])
        {
            ++child;
        }
        if (a[child] < a[parent])
        {
            swap(a[child], a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
            break;
    }
}
//找最大的前K个数
void TopK(int* a, const int n, int k)
{
    assert(n > k && a);
    int* heap = new int[k];
    for (int i = 0; i < k; i++)
    {
        heap[i] = a[i];
    }
    //建堆
    for (int i = (k - 2) / 2; i >= 0; --i)
    {
        AdjustDown(heap,i,k);
    }
    for (int i = k; i < n; i++)
    {
        if (a[i]>heap[0])
            heap[0] = a[i];
        AdjustDown(heap,k,0);
    }
    cout << "最大的前K个数据:" << endl;
    for (int i = 0; i < k; i++)
    {
        cout << heap[i] << " ";
    }
    delete[] heap;
}

2.堆排序问题
降序建小堆,升序建大堆
以降序为例进行解析:
首先建立小堆,根节点为最小的元素,将根节点与最后一个叶子节点进行交换,这样就把最小的元素放到了最后的位置,进行向下调整,除去最后一个结点,变成子问题。如图
这里写图片描述
代码如下:

//降序
void HeapSort(int* a, int n)
{
    assert(a);
    //建小堆
    for (int i = (n - 2) / 2; i < n; i++)
    {
        AdjustDown(a,n,i);
    }
    int end = n - 1;
    while (end>0)
    {
        swap(a[0], a[end]);
        AdjustDown(a,end,0);
        end--;
    }
}

在这里说一下,缺省的优先级队列是利用一个大堆来实现的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值