zoukankan      html  css  js  c++  java
  • Dijkstra算法的线段树优化

    目录

    Dijkstra算法

    算法简介

    实现方法

    利用线段树的优化

    时间复杂度


    Dijkstra算法

    算法简介

    Dijkstra算法是由荷兰计算机科学家Dijkstra于1959年提出的,因此又叫Dijkstra算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题。Dijkstra算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

    (来自百度百科)


    实现方法

    使用dis数组记录目标到各结点的距离。

    通过查询dis数组中的最小值更新其他路径,这个操作我们称作松弛。

    dis[j] = min(dis[j],dis[i] + edge[i][j])

    由上面的转换我们可以知道当前dis数组中的最小值无法被其他值松弛,

    所以我们就可以利用这个最小值来松弛其他边,然后对它做一个标记。

    那么使用flag数组记录已经无法被更新最短路的点。

    (下面是未加优化的Dijkstra算法)

    #include<iostream>
    using namespace std;
    const int edge_size = 100050;
    const int node_size = 10050;
    const int inf = 1e9;
    int n,m,goal;
    int dis[node_size];
    bool flag[node_size];
    int first_edge[node_size];
    struct edge {
        int val,to,next;
        edge() {
    	val = to = next = 0;
        }
        edge(int a,int b,int c) {
    	val = a;
    	to = b;
    	next = c;
        }
    } data[edge_size];//邻接表存图 
    void add_edge(int from,int to,int val,int i) {
        data[i] = edge(val,to,first_edge[from]);
        first_edge[from] = i;
        return;
    }
    int MIN(int a,int b) {
        return a < b ? a : b;
    }
    void Dijkstra() {
        for(int i = 0;i <= n;i++)//初始化 
    	dis[i] = inf;
        for(int i = first_edge[goal];i;i = data[i].next)
    	dis[data[i].to] = MIN(dis[data[i].to],data[i].val);//同一点对间有多条边的情况 
        dis[goal] = 0;
        flag[goal] = true;
        for(int i = 1;i <= n - 1;i++) {
    	int Min = 0;
    	for(int i = 1;i <= n;i++) {//寻找最小dis 
                if(flag[i]) continue;
    	    else if(dis[i] < dis[Min])
    		Min = i;
    	}
            flag[Min] = true;
            for(int i = first_edge[Min];i;i = data[i].next)
    	    if(flag[data[i].to]) continue;
                else dis[data[i].to] = MIN(dis[data[i].to],dis[Min] + data[i].val);//松弛操作 
        }
    }
    void output() {//输出答案 
        for(int i = 1;i <= n;i++)
    	cout << dis[i] << ' ';
        cout << endl;
        return;
    }
    int main() {
        cin >> n >> m >> goal;
        for(int i = 1;i <= m;i++) {
        	int a,b,c;
        	cin >> a >> b >> c;
        	add_edge(a,b,c,i);
        }
        Dijkstra();
        output();
        return 0;
    } 

    利用线段树的优化

    在代码中我们可以看到,每次在寻找最小dis的时候,存在着许多的无用操作;

    寻找最小dis这一行为可以转化为RMQ问题,那么我们就可以使用线段树来优化。

    (直接上代码)

    #include<iostream>
    #include<cstring>
    using namespace std;
    const int edge_size = 100050;
    const int node_size = 10050;
    const int inf = 1e9;
    int n,m,goal;
    int dis[node_size];
    bool flag[node_size];
    int first_edge[node_size];
    struct edge {
        int val,to,next;
        edge() {
    	val = to = next = 0;
        }
        edge(int a,int b,int c) {
    	val = a;
    	to = b;
    	next = c;
        }
    } data[edge_size];//邻接表存图 
    void add_edge(int from,int to,int val,int i) {
        data[i] = edge(val,to,first_edge[from]);
        first_edge[from] = i;
        return;
    }
    int MIN(int a,int b) {
        return a < b ? a : b;
    }
    int tree[node_size * 4],Dis[node_size];//建立一个假的'dis'数组便于维护线段树 
    void update(int pos) {//线段树的更新操作 
        int father = pos / 2;
        while(father >= 1) {
    	int lson = father * 2;
    	int rson = father * 2 + 1;
    	if(Dis[tree[lson]] > Dis[tree[rson]])
                tree[father] = tree[rson];
    	else tree[father] = tree[lson];
    	father /= 2;
        }
    }
    void Dijkstra() {
        memset(tree,0,sizeof(tree));
        for(int i = 0;i <= n;i++)//初始化 
    	Dis[i] = dis[i] = inf;
        for(int i = first_edge[goal];i;i = data[i].next)
    	Dis[data[i].to] = dis[data[i].to] = MIN(dis[data[i].to],data[i].val);
            //同一点对间有多条边的情况 
        Dis[goal] = dis[goal] = 0;
        int leaf_start;//便于查找各结点在线段树中的相应位置 
        for(int i = 0;;i++) {
    	if((1 << i) > n) {
    	    leaf_start = (1 << i) - 1;
    	    break;
    	}
        }
        for(int i = 1;i <= n;i++) {
    	tree[leaf_start + i] = i;
    	update(leaf_start + i);
        }
        Dis[goal] = inf;//在这里我们将已经标记过的点对应的Dis设置为inf,这样便不会影响线段树的查询 
        flag[goal] = true;
        update(leaf_start + goal);
        for(int i = 1;i <= n - 1;i++) {
    	int Min = tree[1];
    	Dis[Min] = inf;
    	flag[Min] = true;
    	update(leaf_start + Min);
    	for(int i = first_edge[Min];i;i = data[i].next)
    	    if(flag[data[i].to]) continue;
    	    else {
    		Dis[data[i].to] = dis[data[i].to] = 
                        MIN(dis[data[i].to],dis[Min] + data[i].val);//松弛操作 
    		update(leaf_start + data[i].to);
    	    }	
        }
    }
    void output() {//输出答案 
        for(int i = 1;i <= n;i++)
    	cout << dis[i] << ' ';
        cout << endl;
        return;
    }
    int main() {
        cin >> n >> m >> goal;
        for(int i = 1;i <= m;i++) {
    	int a,b,c;
    	cin >> a >> b >> c;
    	add_edge(a,b,c,i);
        }
        Dijkstra();
        output();
        return 0;
    } 

    时间复杂度

    线段树的每次更新操作复杂度是O(logn),一共有(m+n)次更新(也可以通过特判使更新次数变得更少)

    所以使用后线段树总的时间复杂度为O((m+n)logn)

    NOIP 2018 RP++
  • 相关阅读:
    浅谈缓存管理
    [Architecture Pattern] Database Migration (上)
    knockout.js的学习笔记4
    cenOS5.5安装oracle10g(傻瓜篇)
    SportsStore:管理 — 精通ASP.NET MVC 3
    ASP.NET MVC下的异步Action的定义和执行原理
    基于EF 4.3.1 Code First的领域驱动设计实践案例
    asp.net缓存
    《Pro ASP.NET MVC 3 Framework》部署
    Asp.Net MVC 3.0【Hello World!】
  • 原文地址:https://www.cnblogs.com/Black-S/p/9819266.html
Copyright © 2011-2022 走看看