堆(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; };
测试用例如下
int main() { Heap<int> h; h.insert(5); h.insert(10); h.insert(1); h.insert(20); cout << h.getMin() << endl; return 0; }
第二种 二叉树
第二种实现利用二叉树来存储元素,它对于 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 : { 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; }
二叉堆
二叉堆是“堆”的默认实现方式。
堆结构两大性质
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; }
d-堆
d-堆是二叉堆的简单推广,它恰像一个二叉堆,只是所有节点都有 d 个儿子(因此,二叉堆是2-堆)。
它将 insert 操作的运行时间改进为 O(logd N)。然而,对于大的 d,deleteMin操作费时得多。
有证据显示,在实践中 4-堆 可以胜过二叉堆。
智慧在街市上呼喊,在宽阔处发声。