堆(Heap)

时间:2020-12-05 11:37:20

  两种简单实现

  第一种 链表

  第一种实现利用链表存储数据,每次在表头插入元素;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;
};

  测试用例如下

int main()
{
Heap<int> h; h.insert();
h.insert();
h.insert();
h.insert(); cout << h.getMin() << endl; return ;
}

  第二种 二叉树

  第二种实现利用二叉树来存储元素,它对于 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;
};

  测试用例如下

int main()
{
Heap<int> bt; for (auto &e : { })
bt.insert(e); for (auto &e : { })
cout << bt.getMin() << endl; for (auto &e : { , , })
bt.insert(e); for (auto &e : { , , })
cout << bt.getMin() << endl; for (auto &e : { , , })
bt.insert(e); for (auto &e : { , , })
cout << bt.getMin() << endl; for (auto &e : { , , , , , , })
bt.insert(e); for (auto &e : { , , , , , , })
cout << bt.getMin() << endl; return ;
}

  二叉堆

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

  堆结构两大性质

  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() {}; void insert(T val)
{
_v.push_back(val); vector<T>::size_type i; for (i = _v.size() - ; _v[i / ] > val; i /= )
{
_v[i] = _v[i / ];
} _v[i] = val;
} T removeMin()
{
if (_v.size() == )
{
return NULL;
} T minElement = _v[];
T lastElement = _v[_v.size() - ]; vector<T>::size_type i, child; for (i = ; i * < _v.size(); i = child)
{
child = i * ; if (child != _v.size() - && _v[child + ] < _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[] = { ,,,, }; Heap<int> h; for (auto e : arr)
{
h.insert(e);
} for (auto e : arr)
{
cout << h.removeMin() << endl;
} return ;
}

  d-堆

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

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

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