1.优先级队列及二叉堆
参考文章:点击这里跳转
我们可以使用数组来模拟二叉堆(本质上还是一棵二叉树)
此处我们只讨论索引从0开始的情况

因此我们可以得到父节点,左右子节点的索引公式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
int parent(int node) { return (node - 1) / 2; }
int left(int node) { return node * 2 + 1; }
int right(int node) { return node * 2 + 2; }
|
核心操作:增
以小顶堆为例,向小顶堆中插入新元素遵循两个步骤:
1、先把新元素追加到二叉树底层的最右侧,保持完全二叉树的结构。此时该元素的父节点可能比它大,不满足小顶堆的性质。
2、为了恢复小顶堆的性质,需要将这个新元素不断上浮(swim),直到它的父节点比它小为止,或者到达根节点。此时整个二叉树就满足小顶堆的性质了。
核心操作:删
以小顶堆为例,删除小顶堆的堆顶元素遵循两个步骤:
1、先把堆顶元素删除,把二叉树底层的最右侧元素摘除并移动到堆顶,保持完全二叉树的结构。此时堆顶元素可能比它的子节点大,不满足小顶堆的性质。
2、为了恢复小顶堆的性质,需要将这个新的堆顶元素不断下沉(sink),直到它比它的子节点小为止,或者到达叶子节点。此时整个二叉树就满足小顶堆的性质了。
用数组模拟二叉堆的原因
1.链表节点需要一个额外的指针存储相邻节点的地址,所以相对数组,链表的内存消耗会大一些。
2.假如我们使用二叉树来构造二叉堆,那么就需要层序遍历或递归遍历二叉树,时间复杂度是 O(N),导致push和pop方法的时间复杂度也为O(N),假如我们使用数组,那么时间复杂度就为O(1)
优先级队列完整代码实现
此处以二叉堆实现为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
| #include <iostream> #include <vector> #include <functional> #include <stdexcept> #include <algorithm>
template<typename T> class MyPriorityQueue{ private: std::vector<T> heap;
int size;
std::function<bool(const T&, const T&)> comparator;
int parent(int node){ return (node - 1) / 2; }
int left(int node){ return node * 2 + 1; }
int right(int node){ return node * 2 + 2; }
void swap(int i, int j){ std::swap(heap[i], heap[j]); }
void resize(int capacity){ heap.resize(capacity); }
void swim(int node){ while(node > 0 && comparator(heap[parent(node)], heap[node])){ swap(parent(node), node); node = parent(node); } }
void sink(int node){ while(left(node) < size){ int min = node; int l = left(node); int r = right(node); if(l < size && comparator(heap[min], heap[l])){ min = l; } if(r < size && comparator(heap[min], heap[r])){ min = r; } if(min == node){ break; } swap(node, min); node = min; } }
public: MyPriorityQueue(int capacity, std::function<bool(const T&, const T&)> comp) : heap(capacity), size(0), comparator(std::move(comp)){}
int getSize() const{ return size; }
bool isEmpty() const{ return size == 0; }
const T& peek() const{ if(isEmpty()){ throw std::underflow_error("Priority queue underflow"); } return heap[0]; }
void push(const T& x){ if(size == heap.size()){ if(heap.size() == 0){ resize(1); } else { resize(2 * heap.size()); } } heap[size] = x; swim(size); size++; }
T pop(){ if(isEmpty()){ throw std::underflow_error("Priority queue underflow"); } T res = heap[0]; swap(0, size - 1); size--; sink(0); if(size > 0 && size <= heap.size() / 4 && heap.size() > 1){ resize(heap.size() / 2); } return res; } };
int main() { MyPriorityQueue<int> pq(3, [](const int& a, const int& b) { return a > b; }); pq.push(3); pq.push(1); pq.push(4); pq.push(1); pq.push(5); pq.push(9);
while (!pq.isEmpty()) { std::cout << pq.pop() << " "; } std::cout << std::endl;
MyPriorityQueue<int> pq2(3, [](const int& a, const int& b) { return a < b; }); pq2.push(3); pq2.push(1); pq2.push(4); pq2.push(1); pq2.push(5); pq2.push(9);
while (!pq2.isEmpty()) { std::cout << pq2.pop() << " "; } std::cout << std::endl;
return 0; }
|
语法部分:
1.什么时候应该用move?
1 2 3 4 5 6 7 8 9 10 11 12 13
|
MyClass(std::vector<int> v) : data(std::move(v)) {}
vector<int> createVector() { vector<int> v = {1, 2, 3}; return v; }
unique_ptr<Widget> ptr1 = make_unique<Widget>(); unique_ptr<Widget> ptr2 = std::move(ptr1);
|
2.std::function<bool(const T&, const T&)> comp
等价写法:
1 2 3 4 5 6 7 8 9
|
std::function<bool(const T&, const T&)> comp;
bool (*comp)(const T&, const T&);
bool (&comp)(const T&, const T&);
|