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)次,总效率为O(NlgE)+Θ(N)=O(NlgE)。考虑到EN2,有O(lgE)=O(lgN2)=O(lgN),则复杂度可以重写为:O(NlgN)

实例程序

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;
}
posted @ 2016-04-23 14:11  ljt12138  阅读(430)  评论(0编辑  收藏  举报