堆(Heap)

  两种简单实现

  第一种 链表

  第一种实现利用链表存储数据,每次在表头插入元素;getMin 时,遍历一遍线性表找到最小的元素,然后将之删除、值返回。(getMax 同理)。

  链表的在头节点的插入和删除时间复杂度都是O(1),所以用链表实现的堆,insert 时间复杂度是O(1)、getMin 时间复杂度是O(n)。

  C++代码如下

template<typename T>
class Heap
{
public:
    void insert(const T &val)
    {
        l.push_front(val);
    }

    T getMin()
    {
        list<T>::iterator itMin = l.begin();

        for (list<T>::iterator it = l.begin(); it != l.end(); it++)
        {
            if (*it < *itMin)
                itMin = it;
        }

        T temp = *itMin;

        l.erase(itMin);

        return temp;
    }

private:
    list<T> l;
};
View Code

  测试用例如下

int main()
{
    Heap<int> h;

    h.insert(5);
    h.insert(10);
    h.insert(1);
    h.insert(20);

    cout << h.getMin() << endl;

    return 0;
}
View Code

  第二种 二叉树

  第二种实现利用二叉树来存储元素,它对于 insert 和 getMin 操作时间复杂度都是 O(log N)。

  C++代码如下

template<typename T>
struct Node
{
    Node(T v) : val(v), left(nullptr), right(nullptr) {};
    T val;
    struct Node* left;
    struct Node* right;
};

template<typename T>
class Heap
{
public:
    Heap() { root = nullptr; }

    void insert(const T &val)
    {
        struct Node<T> **p = &root;

        while (*p != nullptr)
        {
            if (val == (*p)->val)
                return;

            if (val < (*p)->val)
            {
                p = &((*p)->left);
                continue;
            }

            if (val >(*p)->val)
            {
                p = &((*p)->right);
                continue;
            }
        }

        *p = new struct Node<T>(val);
    }

    T getMin() 
    {
        struct Node<T> *p = findMin(root);
    
        T temp = p->val;

        erase(p);

        return temp;
    }

private:
    struct Node<T>* findMin(struct Node<T>* p)
    {
        if (p != nullptr)
            while (p->left != nullptr)
                p = p->left;

        return p;
    }

    struct Node<T>* findFather(T val)
    {
        struct Node<T>* p = root;

        while (p != nullptr)
        {
            if (p->val > val)
            {
                if (p->left->val == val)
                    break;

                p = p->left;
            }

            if (p->val < val)
            {
                if (p->right->val == val)
                    break;

                p = p->right;
            }

            if (p->val == val)
                break;
        }

        return p;
    }

    void erase(struct Node<T>* p)
    {
        struct Node<T>* fatherP = findFather(p->val);

        if (p == root)
        {
            if (p->left == nullptr && p->right == nullptr)
            {
                root = nullptr;
                delete p;
            }
            
            //right
            else if (p->left == nullptr && p->right != nullptr)
            {
                root = p->right;
                delete p;
            }
        }

        //leaf
        else if (p->left == nullptr  &&  p->right == nullptr)
        {
            if (fatherP->left == p)
                fatherP->left = nullptr;

            if (fatherP->right == p)
                fatherP->right = nullptr;

            delete p;
        }

        //right child
        else if (p->left == nullptr  &&  p->right != nullptr)
        {
            fatherP->left = p->right;
            delete p;
        }
    }

    struct Node<T>* root;
};
View Code

  测试用例如下

int main()
{
    Heap<int> bt;

    for (auto &e : { 8})
        bt.insert(e);

    for (auto &e : { 8 })
        cout << bt.getMin() << endl;
    
    for (auto &e : { 8, 3, 1 })
        bt.insert(e);

    for (auto &e : { 8, 3, 1 })
        cout << bt.getMin() << endl;

    for (auto &e : { 8, 9, 10 })
        bt.insert(e);

    for (auto &e : { 8, 9, 10 })
        cout << bt.getMin() << endl;

    for (auto &e : { 8, 5, 10, 4, 6, 9, 11 })
        bt.insert(e);

    for (auto &e : { 8, 5, 10, 4, 6, 9, 11 })
        cout << bt.getMin() << endl;

    return 0;
}
View Code

  二叉堆

  二叉堆是“堆”的默认实现方式。

  堆结构两大性质

  i. 结构性质

  对于数组中任一位置 i 上的元素,其左儿子在位置 2i 上,右儿子在左儿子后的单元 (2i + 1)中,它的父亲则在位置 ⌊i / 2⌋ 上。

  i. 堆序性质

  在一个堆中,对于每一个节点 X, X 的父亲中的关键字小于(或等于)X 中的关键字,根节点除外(它没有父亲)。

  代码实现

#include <iostream>
#include <vector>

using namespace std;

template<typename T>
class Heap
{
public:
    Heap() : _v(1) {};

    void insert(T val)
    {
        _v.push_back(val);

        vector<T>::size_type i;

        for (i = _v.size() - 1; _v[i / 2] > val; i /= 2)
        {
            _v[i] = _v[i / 2];
        }

        _v[i] = val;
    }

    T removeMin()
    {
        if (_v.size() == 1)
        {
            return NULL;
        }

        T minElement = _v[1];
        T lastElement = _v[_v.size() - 1];

        vector<T>::size_type i, child;

        for (i = 1; i * 2 < _v.size(); i = child)
        {
            child = i * 2;

            if (child != _v.size() - 1 && _v[child + 1] < _v[child])
            {
                child++;
            }

            if (lastElement > _v[child])
            {
                _v[i] = _v[child];
            }
            else
            {
                break;
            }
        }

        _v[i] = lastElement;
        _v.pop_back();

        return minElement;
    }

private:
    vector<T> _v;
};

int main()
{
    int arr[] = { 4,3,5,2,6 };

    Heap<int> h;

    for (auto e : arr)
    {
        h.insert(e);
    }

    for (auto e : arr)
    {
        cout << h.removeMin() << endl;
    }

    return 0;
}
View Code

  d-堆

  d-堆是二叉堆的简单推广,它恰像一个二叉堆,只是所有节点都有 d 个儿子(因此,二叉堆是2-堆)。

  它将 insert 操作的运行时间改进为 O(logd N)。然而,对于大的 d,deleteMin操作费时得多。

  有证据显示,在实践中 4-堆 可以胜过二叉堆。

原文地址:https://www.cnblogs.com/fengyubo/p/5186745.html