zkw神树优化dijkstra
zkw神树
zkw神树不多说了,不懂的移步我的博客《zkw线段树》。这里优化dij用,不需要区间查询,只使用点修改一个,编程复杂度极低。
唯一不同的是的为了更快找到最小值的位置(dijkstra中点的编号),需要加一个mark标记,记录最小值逻辑上的位置,或者说图里面点的编号。
奉上一个做好的zkwheap模板,不光在这里能用,是个能修改元素值的堆。
template < typename T > struct ZkwHeapNode {
T value;
size_t mark;
// 编号
ZkwHeapNode() { }
ZkwHeapNode(const T & _value):value(_value) {}
};
template < typename T, typename Comp > class ZkwHeap {
private:
typedef ZkwHeapNode < T > Node;
typedef ZkwHeap < T, Comp > Heap;
Comp cmp;
Node *NodeList;
size_t n;
// 最多元素
int init_value;
// 初始值,大根堆(less<>)就用无穷小,小根堆(greater<>)就用无穷大
void fix(size_t _pos) {
if (cmp(NodeList[_pos << 1].value, NodeList[(_pos << 1) + 1].value))
NodeList[_pos] = NodeList[(_pos << 1) + 1];
else
NodeList[_pos] = NodeList[_pos << 1];
}
public:
ZkwHeap(size_t _MaxN, const T & _init_value):init_value(_init_value) {
n = 1 << (1 + (size_t) (log(_MaxN) / log(2.0)));
NodeList = new Node[n << 1];
for (size_t i = 1; i <= n + n - 1; i++)
NodeList[i].value = init_value;
for (size_t i = n; i <= n + n - 1; i++)
NodeList[i].mark = i - n + 1;
// 分配标记
}
~ZkwHeap() {
delete[]NodeList;
}
T top() {
return NodeList[1].value;
}
// 最大元素
T top_pos() {
return NodeList[1].mark;
}
// 最大元素位置
void modify(size_t _position, const T & _new_value) {
int _pos = _position + n - 1;
NodeList[_pos].value = _new_value;
while (_pos)
fix(_pos >>= 1);
}
T pop() {
T return_value = NodeList[1].value;
modify(NodeList[1].mark, init_value);
return return_value;
// 删除极值
}
};
理解了zkw线段树,也就很容易明白为什么用zkw线段树优化dijkstra了。
堆优化dijkstra分析
dijkstra算法效率是Θ(N^2)。事实上,运用邻接表+堆优化可是将时间复杂度降到O(NlgE)。我们将在最后用聚合分析得出这个界。
为什么要堆优化?因为dijkstra最大的时间复杂度消耗在寻找dis[]中的最小值上。而用小根堆就解决了这个问题。伪代码如下:
function dijkstra(G, s) {
H = a new heap
dis = a new array contain ∞
H中s的值设为0
for (int i=2; i<=G.n; i++) {
k = H中dis最小的点
dis[k] = k.dis
// 记录下来
for each p connected to k {
if (dis[p]>dis[k]+w[k][p]) {
dis[p]=dis[k]+w[k][p]
修改H中p的dis
}
}
}
}
不难发现,普通二叉堆对这样的问题不能完美解决,相比之下,zkw线段树就自然的多了。直接给出一段代码:
void dijkstra(int from) {
memset(dis, 127 / 3, sizeof(dis));
dis[from] = 0;
tree.modify(from, 0);
for (int i = 2; i <= n; i++) {
int kn = tree.top_pos();
dis[kn] = tree.pop();
if (kn == T) {
cout << dis[T] << endl;
return;
}
for (int k = head[kn]; k; k = graph[k].next) {
if (dis[graph[k].to] > dis[kn] + graph[k].dis) {
dis[graph[k].to] = dis[kn] + graph[k].dis;
tree.modify(graph[k].to, dis[graph[k].to]);
}
}
}
cout << dis[T] << endl;
}
几乎是完全按照伪代码写出。
效率分析
以前写zkw线段树的博文已经分析过,zkw线段树的修改复杂度为Θ(lgN)。在这里的程序中,我们需要证明内层的循环次数不会超过O(E),从而证明zkw优化dijkstra的效率为O(NlgE)。显然,在没有负权值的图中,一条边不可能有两次被松弛。聚合分析一下,内层for循环总共至多进行O(E)次,总效率为
实例程序
tyvj 热浪一题,zkw优化dijkstra标程:
#include <cmath>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;
template < typename T > struct ZkwHeapNode {
T value;
size_t mark;
ZkwHeapNode() {
} ZkwHeapNode(const T & _value):value(_value) {
}
};
template < typename T, typename Comp > class ZkwHeap {
private:
typedef ZkwHeapNode < T > Node;
typedef ZkwHeap < T, Comp > Heap;
Comp cmp;
Node *NodeList;
size_t n;
int init_value;
void fix(size_t _pos) {
if (cmp(NodeList[_pos << 1].value, NodeList[(_pos << 1) + 1].value))
NodeList[_pos] = NodeList[(_pos << 1) + 1];
else
NodeList[_pos] = NodeList[_pos << 1];
}
public:
ZkwHeap(const size_t & _MaxN, const T & _init_value):init_value(_init_value) {
n = 1 << (1 + (size_t) (log(_MaxN) / log(2.0)));
NodeList = new Node[n << 1];
for (size_t i = 1; i <= n + n - 1; i++)
NodeList[i].value = init_value;
for (size_t i = n; i <= n + n - 1; i++)
NodeList[i].mark = i - n + 1;
}
~ZkwHeap() {
delete[]NodeList;
}
T top() {
return NodeList[1].value;
}
T top_pos() {
return NodeList[1].mark;
}
void modify(size_t _position, const T & _new_value) {
int _pos = _position + n - 1;
NodeList[_pos].value = _new_value;
while (_pos)
fix(_pos >>= 1);
}
T pop() {
T return_value = NodeList[1].value;
modify(NodeList[1].mark, init_value);
return return_value;
}
};
const int maxPoint = 2505;
const int maxEdge = 70005;
struct p {
int to, dis, next;
} graph[maxEdge];
int head[maxPoint];
int pointer = 0;
int dis[maxPoint];
int n, m, S, T;
int zkn;
ZkwHeap < int, greater < int >>tree(maxPoint, 100000000);
void init() {
memset(head, 0, sizeof(head));
memset(dis, 127 / 3, sizeof(head));
}
void push_edge(int from, int to, int dis) {
graph[++pointer].to = to;
graph[pointer].dis = dis;
graph[pointer].next = head[from];
head[from] = pointer;
}
void dijkstra(int from) {
memset(dis, 127 / 3, sizeof(dis));
dis[from] = 0;
tree.modify(from, 0);
for (int i = 2; i <= n; i++) {
int kn = tree.top_pos();
dis[kn] = tree.pop();
if (kn == T) {
cout << dis[T] << endl;
return;
}
for (int k = head[kn]; k; k = graph[k].next) {
if (dis[graph[k].to] > dis[kn] + graph[k].dis) {
dis[graph[k].to] = dis[kn] + graph[k].dis;
tree.modify(graph[k].to, dis[graph[k].to]);
}
}
}
cout << dis[T] << endl;
}
int main() {
init();
cin >> n >> m >> S >> T;
zkn = (1 << (int)((int)log(n) / log(2) + 1));
int from, to, dis;
for (int i = 1; i <= m; i++) {
cin >> from >> to >> dis;
push_edge(from, to, dis);
push_edge(to, from, dis);
}
dijkstra(S);
return 0;
}