堆———创建堆、优先级队列、Topk、堆排序


堆是数据结构的一种,堆分为大堆和小堆

堆的概念:堆是一颗完全二叉树,通常情况下我们用数组来表示堆,在下面我用的是vector其实也就是数组;大堆(是一颗二叉树,它的特点是父亲节点大于它左右孩子);小堆(即就是它的父亲节点小于它的左右孩子);


堆的实现

这里我们采用适配器模式来实现大小堆,避免一些重复的代码;

首先我们的了解堆一般都那些接口,push、pop、size、top、empty;

堆的实现的核心算法:向下调整、向上调整

当我们在构建一个堆时的用到向下调整算法,删除时也会用到向下调整算法;当插入一个元素时会用到向上调整算法。

向下调整算法:假设一棵有三个节点二叉树,那么调整它的时候只需要选出他左右孩子中那个的最大或者最小,然后交换最大或最小的孩子节点和根节点(即向下调整);那么当它有多个节点时只需从它的最后一个非叶子节点开始倒着向下调整。

向上调整算法:当插入一个数时,插入的数只会影响和他同一路径的祖先节点,所以从它开始向根节点调整整(向上调整)。


代码实现堆——heap

#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;
//堆,向下调整建堆
//当删除一个元素时,将它与堆的末尾元素交换换,删除(vertor)末尾元素,对堆进行向下调整
//当插入一个元素时,插入堆的(vector)末尾,进行向上调整
template<class T>
struct Less//仿函数
{
    bool operator()(const T& l, const T& r)
    {
        return l < r;
    }
};
template<class T>
struct Greater//仿函数
{
    bool operator()(const T& l, const T& r)
    {
        return l > r;
    }
};
template<class T, class Compare = Less<T>>//适配器
class heap
{
public:
    heap()
    {}
    heap(T* a, size_t n)
    {
        _a.resize(n);
        for (size_t i = 0; i < n; i++)
        {
            _a[i] = a[i];//vector 使用[]时,必须进行对它初始化(开辟一块空间)_a.resize
        }
        for (int j = (_a.size() - 2) / 2; j >= 0; j--)
            //从非叶子节点开始调整,_a.size()是元素个数,
            //_a.size()-1是最后一个节点的位置,
            //非叶子节点的位置,是最后一个节点的父亲,
            //父亲节点在vector位置等于 (_a.size()-1-1)/2
        {
            AdjustDown(j);
        }
    }
    void Push(const T& data)
    {
        _a.push_back(data);
        AdjustUp(_a.size()-1);
    }
    void Pop()
    {
        assert(_a.size() != 0);
        swap(_a[0],_a[_a.size()-1]);
        _a.pop_back();
        AdjustDown(0);
    }
    void print()
    {
        vector<int> ::iterator it = _a.begin();
        while (it != _a.end())
        {
            cout << *it << " ";
            it++;
        }
        cout << endl;
    }
    size_t Size()
    {
        return _a.size();
    }
    bool Empty()
    {
        return _a.empty();
    }
    const T& Top()
    {
        assert(!_a.empty());
        return _a[0];
    }
private:
    void AdjustDown(int root)
    {
        int parent = root;
        int child = 2 * parent+1;
        Compare com;
        //从左右孩子里选出大的孩子
        while (child < _a.size())
        {
            if (child+1 < _a.size() && com(_a[child+1], _a[child]))
            {
                ++child;
            }
            if (com(_a[child], _a[parent]))
            {
                swap(_a[child], _a[parent]);
                parent = child;
                child = 2 * parent + 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;
        }
    }
public:
    vector<T> _a;
};


优先级堆列

优先级队列的概念:如果我们给每个元素都分配一个数字来标记其优先级,不妨设较小的数字具有较高的优先级,这样我们就可以在一个集合中访问优先级最高的元素并对其进行查找和删除操作了。这样,我们就引入了优先级队列 这种数据结构。 优先级队列(priority queue) 是0个或多个元素的集合,每个元素都有一个优先权,对优先级队列执行的操作有(1)查找(2)插入一个新元素 (3)删除 一般情况下,查找操作用来搜索优先权最大的元素,删除操作用来删除该元素 。对于优先权相同的元素,可按先进先出次序处理或按任意优先权进行。

总的来说一句话:大小堆问题


代码实现——priority_queue

template<class T,class Compare>//适配器
class PriorityQueue
{
public:
    void Push(const T& data)
    {
        _h.Push(data);
    }
    void Pop()
    {
        _h.Pop();
    }
    const T& Top()
    {
        return _h.Top();
    }
public:
    heap<T, Compare> _h;
};


TopK

TopK概念:海量数据中的前K个最大或者最小的数。

下面实现前K个最大的数
实现思路:我们要求一组数据中的前K个最大数需要一个小堆,先将数组中的前K个数据入到堆中进行建立小堆;然后将剩下的数与堆顶比较如果大于堆顶就交换,然后重新调整,这样最大数会慢慢的往树的下面移动,这样堆顶数将会这K个数里最小的一个,当这组数据全部都进入堆后,前K最大的数就在堆里。

代码实现TopK

//TopK问题,对于大量数据找出大量数据中前K(最大)的数
void TopK(const vector<int>& v, const int& K)
{
    //先从v中取出K个数并进行建堆
    //建小堆
    assert(K<v.size());
    vector<int> TopK;
    TopK.resize(K);
    for (int i = 0; i < K; i++)
    {
        TopK[i] = v[i];
    }
    //建小堆
    for (int i = (K - 2) / 2; i >= 0; i--)//(k-2)/2是最后一个非叶子节点
    {
        AdjustDown(TopK,i);
    }
    //T = (N-K)*lgK
    for (size_t i = K; i < v.size(); i++)//将所有数据入堆,如果比堆顶数据大,就替换掉堆顶并重新调整
    {
        if (TopK[0] < v[i])
        {
            TopK[0] = v[i];
            AdjustDown(TopK,0);
        }
    }
    vector<int> ::iterator it = TopK.begin();
    while (it != TopK.end())
    {
        cout << *it << " ";
        it++;
    }
    cout << endl;
}

向下调整算法:

void AdjustDown(vector<int>& TopK, const size_t& root)
{
    int parent = root;
    int child = parent * 2 + 1;
    while(child < TopK.size())
    {
        if (child + 1 < TopK.size())
        {
            if (TopK[child] > TopK[child + 1])
                child++;
        }
        if (TopK[parent]>TopK[child])
        {
            swap(TopK[parent], TopK[child]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
            break;
    }
}


堆排序

概念:即就是用堆来完成排序工作

堆排序算法:将所有要排数据,入到一个堆里(升序用大堆,降序用小堆),然后进行交换,交换堆顶元素和最后一个元素(从第一次开始最后一个是数组的最后一个(这样最大或最小的就在数组的最后面),第二次交换的最后一个是数组的倒数第二个。。。。依次类推,当交换到第二个时,排序就完成了)。


代码实现———

//堆排序,heapsort
void AdjustDown(int* a, int size, int root)
{
    int parent = root;
    int child = 2 * parent + 1;
    while (child < size)
    {
        if (child + 1 < size)
        {
            if (a[child + 1] > a[child])
                child++;
        }
        if (a[child + 1]>a[parent])
        {
            swap(a[parent], a[child]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
            break;
    }
}
void HeapSort(int* a, int size)
{
    for (int i = (size - 2) / 2; i >= 0; i--)//建大堆
    {
        AdjustDown(a,size,i);
    }
    //排序
    int end = size - 1;
    while (end > 1)
    {
        swap(a[end],a[0]);
        AdjustDown(a, end, 0);
        --end;
    }
    for (int i = 0; i < size; i++)
    {
        cout << a[i] << " ";
    }
    cout << endl;
}

猜你喜欢

转载自blog.csdn.net/fangxiaxin/article/details/79574711