zoukankan      html  css  js  c++  java
  • Dijkstra算法

    单源最短路问题

    给定一张 (n) 个点的有向图,边权均为正数。给定起点 (st) ,要求出 (st) 到其他所有点的最短路。

    算法简介

    ( ext{Dijkstra}) 算法是典型的单源最短路径算法,朴素算法的时间复杂度为 (O(n^2)) ,加上堆优化后可以达到 (O((n+m)log n)) 的时间复杂度,并且在实际应用中较为稳定,需要在各种题目中灵活应用!

    算法流程

    ( ext{Dijkstra}) 算法的主要思想是贪心,但它只适用于不含负权边的图。

    它的主要思想是:以起始点为中心向外层层扩展,直到扩展到终点为止。

    1. 初始化

    (d[i]) 表示起点 (st)(i) 点的最短距离,并把初始值为无穷大 (+∞)(d[st]=0) 。把点集 (V) 分成两部分:已经确定最短路的点集 (S) 与未确定最短路的点集。我们先将 (S) 初始化为空集。

    1. 具体过程

    先找到一个不在 (S)(d) 值最小的点 (u) ,然后将它加入到 (S) 中。

    再扫描节点 (u) 所有出边 ((u,v,w)) ,对所有 (v) 做一次松弛操作,即若 (d[v]>d[u]+w) ,则用 (d[u]+w) 更新(d[v])

    重复上述步骤,直到所有节点被加入到 (S) 中。

    正确性的简要证明

    我们知道,当所有边长都是非负数的时候,全局最小值不可能再被其他节点更新。即在第 (2) 步中找出的 (u) 一定不会存在其它的点再来更新 (d[u]) 此时的 (d[u]) 已经是从起点到 (u) 的最短路径。我们不断选择确定为最小值的点进行标记和拓展,最终就可以得到起点到每个节点的最短路径。

    朴素算法

    void dijkstra()
    {
    	bool vis[N];
    	memset(d,0x3f,sizeof d);
    	memset(vis,0,sizeof vis);
    	d[st]=0;
    	while(1)
    	{
    		int u=-1;
    		for(Re int i=1;i<=n;i++)
    		{
    			if(!vis[i]&&(u==-1||d[i]<d[u]))
    			{
    				u=i;
    			}
    		}
    		if(u==-1) break;
    		vis[u]=1;
    		for(Re int i=0;i<g[u].size();i++)
    		{
    			int to=g[u][i].id;
    			int Len=g[u][i].d;
    			if(d[to]>d[u]+Len)
    			{
    				d[to]=d[u]+Len;
    			}
    		}
    	}
    }
    

    算法优化

    然而,我们发现该程序的时间复杂度虽然只有 (O(n^2)) ,但仍然无法满足大多数题目的要求。

    于是,我们便考虑用一些操作来优化上述步骤。

    优先队列 ( priority_queue )

    这就是所谓的堆优化 ( ext{Dijkstra}) ,是较为常用的优化方式。

    我们考虑用堆对 (d) 数组进行维护,共进行的 (n) 次操作,每次用 (O(log n)) 的时间取出堆顶元素并删除;共用 (O(mlog n)) 遍历每条边。故总时间复杂度为 (O((n+m)log n))

    struct Node {
    	int id,d;
    	bool operator < (const Node &a) const{
    		return d>a.d;
    	}
    };
    
    void dijkstra()
    {
    	bool vis[N];
    	memset(d,0x3f,sizeof d);
    	memset(vis,0,sizeof vis);
    	d[st]=0;
    	priority_queue<Node> q;
    	q.push((Node){st,0});
    	while(!q.empty())
    	{
    		int u=q.top().id;
    		q.pop();
    		if(vis[u]) continue;
    		vis[u]=1;
    		for(Re int i=0;i<g[u].size();i++)
    		{
    			int to=g[u][i].id;
    			int Len=g[u][i].d;
    			if(d[to]>d[u]+Len)
    			{
    				d[to]=d[u]+Len;
    				q.push((Node){to,d[to]});
    			}
    		}
    	}
    }
    

    但是上述代码需要事先写一个结构体,有没有办法可以避免呢?

    于是我们考虑使用 ( ext{pair}) 数组。

    pair 数组

    由于 ( ext{pair}) 数组有自带的比较函数,因此,我们可以省去上述的结构体。

    但是我们需要注意一点,就是在入队的时候需要加入的是 (-d[v]) 而不是 (d[v])

    void dijkstra()
    {
    	bool vis[N];
    	memset(d,0x3f,sizeof d);
    	memset(vis,0,sizeof vis);
    	d[st]=0;
    	priority_queue<pair<int,int> > q;
    	q.push(make_pair(0,st));
    	while(!q.empty())
    	{
    		int u=q.top().second;
    		q.pop();
    		if(vis[u]) continue;
    		vis[u]=1;
    		for(Re int i=0;i<g[u].size();i++)
    		{
    			int to=g[u][i].id;
    			int Len=g[u][i].d;
    			if(d[to]>d[u]+Len)
    			{
    				d[to]=d[u]+Len;
    				q.push(make_pair(-d[to],to));
    			}
    		}
    	}
    }
    
  • 相关阅读:
    爬虫心得
    WSL windows子系统ubuntu18.04建设自己的乌云
    WSL windwos 子系统 ubuntu18.04安装mysql
    python 163 email 554
    Centos 安装Oracle
    JS带进度 文件 重复 自动 异步上传
    xadmin 小组件默认折叠
    grep
    sed
    awk
  • 原文地址:https://www.cnblogs.com/kebingyi/p/14015661.html
Copyright © 2011-2022 走看看