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++
  • 相关阅读:
    pipelinewise 学习二 创建一个简单的pipeline
    pipelinewise 学习一 docker方式安装
    Supercharging your ETL with Airflow and Singer
    ubuntu中使用 alien安装rpm包
    PipelineWise illustrates the power of Singer
    pipelinewise 基于singer 指南的的数据pipeline 工具
    关于singer elt 的几篇很不错的文章
    npkill 一个方便的npm 包清理工具
    kuma docker-compose 环境试用
    kuma 学习四 策略
  • 原文地址:https://www.cnblogs.com/Black-S/p/9930717.html
Copyright © 2011-2022 走看看