• 关于最短路



    [前言&胡扯]

    (By:Soroak)
    说到最短路问题,我们无非就是用三种算法进行处理:
    ①: (Floyd) 。②: (Dijkstra) 。③: (Spfa)
    对于最短路算法,学长之前给我们讲过,可是当时我因为不想听课,上课走神等原因,成功的没有学扎实,现在来重新 复习 重新学习一下


    [Floyd]

    • 定义: (Floyd) 算法,是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。
    • 时间&空间复杂度:
      (Floyd) 算法的时间复杂度为 (O(N^3)) ,空间复杂度为 (O(N^2))
    • (Floyd) 可以说是最暴力的一个算法了,用 (Floyd) 的时候,往往用邻接矩阵来存储。
      Q:为什么用邻接矩阵来存而不是邻接表来存呢??
      A:没有那个必要啊,Floyd的时间复杂度是 (O(N^3))(O(N^3)) 能跑的范围,邻接矩阵完全可以存过来,不过你要是实在想写邻接表来存,我也不拦你用邻接表来存 $ Floyd$ 用 (1s) 就 能跑过来的图。。。

    以下是 (Floyd) 的模板:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define int long long int 
    
    using namespace std;
    
    int map[5010][5010];
    int n,m;
    
    const int INF=0x7fffffff; 
    
    signed main()
    {
    	cin>>n>>m;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=n;j++)
    		{
    			map[i][j]=INF;
    		}
    	}
    	for(int i=1;i<=n;i++)
    	{
    		map[i][i]=0;
    	}
    	for(int i=1;i<=m;i++)
    	{
    		int u,v,w;
    		cin>>u>>v>>w;
    		map[u][v]=w;
    		map[v][u]=w;
    	}
    	for(int k=1;k<=n;k++)
    	{
    		for(int i=1;i<=n;i++)
    		{
    			for(int j=1;j<=n;j++)
    			{
    				if(map[i][j]>map[i][k]+map[k][j]&&k!=i&&k!=j&&i!=j)
    				{
    					map[i][j]=map[i][k]+map[k][j];
    				}				
    			}
    		}
    	}
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=n;j++)
    		{
    			cout<<map[i][j]<<" ";
    		}
    		cout<<endl;
    	}
    	return 0;
    } 
    
    /*样例输入
    4 6 
    1 2 2
    2 3 2
    2 4 1
    1 3 5
    3 4 3
    1 4 4
    输出:
    0 2 4 3
    2 0 2 1
    4 2 0 3
    3 1 3 0
    *、
    

    [Dijkstra及其优化]

    • 定义: (Dijkstra) 算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

    • Q&A:
      Q: (Dijkstra)(Floyd) 好在哪里呢??
      A:上面说过 (Dijkstra) 是典型的单源最短路径算法,何谓单源最短路??就是给定一个起始点,求这个点到所有点的最短路,求一遍 (Dijkstra) (非优化)的时间复杂度是 (O(N^2)) ,你要是想求出每个点到每个点的最短路,我还是建议你用 (Floyd) ,因为 (Dijkstra)(N) 遍单源最短路的复杂度和 (Floyd) 一样都是 (O(N^3)) ,当然,这里说的都是未优化的情况,优化的 (Dijkstra) 下面会讲到。

    • (Dijkstra) 是一种类似于贪心的算法,具体步骤:

      • 当到一个时间点时,图上已经有一部分的点,最短路径已经确定,而部分点尚未确定。
      • 在所有未确定的点中选择离原点最近的点,把它认为是这两个点之间的最短距离。
      • 然后把这个点所有的出边都遍历一边,并更新所有点。

    (Dijkstra) 从随意一个点出发,到达各点的最短路径长度的代码如下:

    //#include<iostream>
    //#include<cstdio>
    //#include<cstring>
    //#define int long long int 
    //
    //using namespace std;
    //
    //int map[5010][5010];
    //int dis[5010];
    //int fl[5010];
    //int n,m,s;
    //const int inf=2147483647;
    //
    //inline void dijkstra(int u)
    //{
    //	memset(dis,63,sizeof(dis));
    //	int start=u;
    //	fl[start]=1;
    //	for(int i=1;i<=n;i++)
    //	{
    //		dis[i]=min(dis[i],map[start][i]);
    //	}
    //	for(int i=1;i<=n-1;i++)
    //	{
    //		int minn=inf;
    //		for(int j=1;j<=n;j++)
    //		{
    //			if(fl[j]==0&&minn>dis[j])
    //			{
    //				minn=dis[j];
    //				start=j;
    //			}
    //		}
    //		fl[start]=1;
    //		for(int j=1;j<=n;j++)
    //		{
    //			dis[j]=min(dis[j],dis[start]+map[start][j]);
    //		}
    //	}
    //}
    //
    //signed main()
    //{
    //	cin>>n>>m>>s;
    //	memset(map,63,sizeof(map));
    //	for(int i=1;i<=m;i++)
    //	{
    //		int a,b,c;
    //		cin>>a>>b>>c;
    //		map[a][b]=c;
    //		map[b][a]=c;
    //	}
    //	for(int i=1;i<=n;i++)
    //	{
    //		map[i][i]=0;
    //	}
    //	dijkstra(s);
    //	for(int i=1;i<=n;i++)
    //	{
    //		cout<<dis[i]<<" ";
    //	}
    //	return 0;
    //}
    
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define int long long int 
    
    using namespace std;
    
    int value[10010];
    int to[10010];
    int nxt[10010];
    int head[10010];
    int total;
    int fl[10010];
    int dis[10010];
    int n,m,s;
    
    inline void add(int a,int b,int c)
    {
    	total++;
    	to[total]=b;
    	value[total]=c;
    	nxt[total]=head[a];
    	head[a]=total;
    }
    
    void dijkstra(int u)
    {
    	memset(dis,63,sizeof(dis));
    	memset(fl,0,sizeof(fl));
    	dis[u]=0;
    	for(int i=1;i<n;i++)
    	{
    		int start=-1;
    		for(int j=1;j<=n;j++)
    		{
    			if(fl[j]==0&&(dis[start]>dis[j]||start==-1))
    			{
    				start=j;
    			}
    		}
    		fl[start]=1;
    		for(int e=head[start];e;e=nxt[e])
    		{
    			dis[to[e]]=min(dis[to[e]],dis[start]+value[e]);
    		}
    	}
    }
    
    signed main()
    {
    	cin>>n>>m>>s;
    	for(int i=1;i<=m;i++)
    	{
    		int a,b,c;
    		cin>>a>>b>>c;
    		add(a,b,c);
    		add(b,a,c);
    	}
    	dijkstra(s);
    	for(int i=1;i<=n;i++)
    	{
    		cout<<dis[i]<<" ";
    	}
    	cout<<endl;
    	return 0;
    }
    
    • Q&A
      Q:那 (Dijkstra) 的时间复杂度是 (O(N^2)) ,那跑 (N) 遍单元最短路的复杂度不就是 (O(N^3)) 了吗,那和 (Floyd) 没有什么区别啊
      A:别着急,上面的是未优化的,我们还有堆优化过的 (Dijkstra),跑一遍的时间复杂度就成了 (O(NlogN))

    下面上代码:
    (感谢gyh大佬的大力支持)

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    
    const int INF=2147483647;
    const int MARX=1e5+10;
    using namespace std;
    
    struct edge
    {
    	int u,v,w,ne;
    }e[MARX<<1];
    
    struct p
    {
    	int num,diss;
    	bool operator<(const p &a)const
    	{
    		return diss>a.diss;
    	}
    }tmp;
    
    int head[MARX],dis[MARX];
    bool f[MARX]; 
    int num,n,m,s,x;
    
    void add(int u,int v,int w)
    {
    	e[++num].ne=head[u];
    	head[u]=num;
    	e[num].u=u;
    	e[num].v=v;
    	e[num].w=w;
    }
    
    void dj(int s)
    {
    	priority_queue <p> q;
    	tmp.num=s;
    	tmp.diss=0;
    	q.push(tmp);
    	for(int i=1;i<=n;i++) 
    	{
    		dis[i]=INF; 
    	}
    	dis[s]=0;
    	while(!q.empty())
    	{
    		int top=q.top().num; 
    		q.pop();
    		if(f[top]) 
    		{
    			continue;
    		}
    		f[top]=1;
    		for(int j=head[top];j;j=e[j].ne)//找k点的临点,并进行比较 
    		{
    			if(dis[e[j].v] > dis[top]+e[j].w && (!f[e[j].v]))
    			{
    				dis[e[j].v] = dis[top]+e[j].w;
    				tmp.num=e[j].v;
    				tmp.diss=dis[e[j].v];
    				q.push(tmp);
    			}
    		}
    	}
    }
    
    signed main()
    {
    	scanf("%d%d%d",&n,&m,&s);
    	for(int i=1;i<=m;i++)
    	{
    		int u,v,w;
    		scanf("%d%d%d",&u,&v,&w);
    		add(u,v,w);
    	}
    	dj(s);
    	for(int i=1;i<=n;i++) 
    	{
    		printf("%d ",dis[i]);
    	}  
    	return 0; 
    }
    
    /*
    //
    By:Luckyblock
    使用STL中的 pair类实现 
    简单好写 
    #include<cstdio>
    #include<cstring>
    #include<ctype.h>
    #include<queue>
    #define int long long
    const int MARX = 2e6+10;
    //=============================================================
    struct edge
    {
    	int u,v,w,ne;
    }e[MARX<<1];
    int n,m,num, head[MARX];
    int dis[MARX];
    bool vis[MARX];
    //=============================================================
    inline int read()
    {
        int s=1, w=0; char ch=getchar();
        for(; !isdigit(ch);ch=getchar()) if(ch=='-') s =-1;
        for(; isdigit(ch);ch=getchar()) w = w*10+ch-'0';
        return s*w;
    }
    void add(int u,int v,int w)
    {
    
    	e[++num].u = u,e[num].v = v, e[num].w = w;
    	e[num].ne = head[u], head[u] = num;
    }
    void dijkstra(int start)
    {
    	std::priority_queue <std::pair<int,int> > q;
    	memset(dis,63,sizeof(dis));
    	dis[start] = 0 ;
    	q.push(std::make_pair(0,start));
    	
    	for(; !q.empty(); )
    	{
    	  std::pair <int,int> top = q.top();  q.pop();
    	  if(vis[top.second]) continue;
    	  vis[top.second] = 1;
    	  
    	  for(int i=head[top.second]; i; i = e[i].ne)
    	    if(dis[e[i].v] > dis[e[i].u] + e[i].w)
    	    {
    	      dis[e[i].v] = dis[e[i].u] + e[i].w;
    	      q.push(std::make_pair(-dis[e[i].v], e[i].v));
    		}
    	}
    }
    //=============================================================
    signed main()
    {
    	n = read(), m = read();
    	int s = read();
    	for(int i=1; i<=m; i++) 
    	{
    	  int u = read(), v = read(), w = read();
    	  add(u,v,w);
    	}
    	dijkstra(s);
    	for(int i=1; i<=n; i++) printf("%lld ",dis[i]);
    }
    */
    

    [SPFA及其优化]

    • 定义:(SPFA) 算法是 (Bellman-Ford) 算法 的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。
    • (SPFA) 一般是用于判断负环的,要是题目中给的图并没有负边权,那么还是建议用 (Dijkstra) 。。。
    • Q&A
      Q:那怎么判断是否有负环呢??
      A:其实很简单,就只要判断一下是否有一个点入队次数超过 (N) 就好了。
    • 实现方法:建立一个队列,初始时队列里只有起始点,在建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点去刷新起始点到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。

    (SPFA) 代码以及优化:(在这里再次感谢gyh大佬的支持)

    #include<cstdio>
    #include<queue>
    #define INF 2147483647
    
    using namespace std;
    
    queue<int>q;
    
    struct edg
    {
    	int u,v,next;
    	int w;
    } edge[5000500];
    
    int head[3000000];
    int ans[3000000],vis[3000000];
    int num=0;
    int s,t;
    
    void build(int u,int v,int w)
    {
    	edge[++num].next=head[u];
    	head[u]=num;
    	edge[num].u=u;
    	edge[num].v=v;
    	edge[num].w=w;
    }
    
    void SPFA(int s)
    {
    	ans[s]=0;
    	vis[s]=1;
    	q.push(s);
    	while(!q.empty())
    	  {
    		int u=q.front();
    		q.pop();
    		vis[u]=0;
    		for(int i=head[u];i;i=edge[i].next)
    		  {
    			int v=edge[i].v;
    			if(ans[v]>edge[i].w+ans[u])
    			  {
    				ans[v]=edge[i].w+ans[u];
    				if(!vis[v])
    				  {
    					q.push(v);
    					vis[v]=1;
    				  }
    			  }
    
    		  }
    	  }
    }
    
    int main()
    {
    	int n,m,s;
    	scanf("%d%d%d",&n,&m,&s);
    	for(int i=1; i<=n; i++)
    	  ans[i]=INF;
    	for(int i=1; i<=m; i++)
    	  {
    		int a,b,c;
    		scanf("%d%d%d",&a,&b,&c);
    		build(a,b,c);
    	  }
    	SPFA(s);
    	for(int i=1; i<=n; i++)
    	  printf("%d ",ans[i]);
    	return 0;
    }
    
    //==========简单好用优先队列优化==========
    //优化很成功,时间复杂度较低,吊打DJ BOBO
    /*
    //将queue改为priority_queue,注意将q.front()改为q.top()
    //同时自定义优先级 , 以最短路径长度升序排列 
    #include<cstdio>
    #include<queue>
    #define INF 2147483647
    using namespace std;
    
    struct edg
    {
    	int u,v,next;
    	int w;
    } edge[5000500];
    int head[3000000];
    int ans[3000000],vis[3000000];
    int num=0;
    int s,t;
    struct cmp1
    {
        bool operator ()(const int a,const int b)
          {
            return ans[a]>ans[b];
          }
    };
    priority_queue <int,vector<int>,cmp1> q;
    void build(int u,int v,int w)
    {
    	edge[++num].next=head[u];head[u]=num;
    	edge[num].u=u;edge[num].v=v;edge[num].w=w;
    }
    void SPFA(int s)
    {
    	ans[s]=0;
    	vis[s]=1;
    	q.push(s);
    	while(!q.empty())
    	{
    	  int u=q.top();
    	  q.pop();
    	  vis[u]=0;
    	  for(int i=head[u]; i; i=edge[i].next)
    	  {
    		int v=edge[i].v;
    		if(ans[v]>edge[i].w+ans[u])
    		{
    		  ans[v]=edge[i].w+ans[u];
    		  if(!vis[v])
    		  {
    			q.push(v);
    			vis[v]=1;
    		  }
    		}
    	  }
    	}
    }
    int main()
    {
    	int n,m,s;
    	scanf("%d%d%d",&n,&m,&s);
    	for(int i=1; i<=n; i++) ans[i]=INF;
    	for(int i=1; i<=m; i++)
    	{
    	  int a,b,c;
    	  scanf("%d%d%d",&a,&b,&c);
    	  build(a,b,c);
    	}
    	SPFA(s);
    	for(int i=1; i<=n; i++)
    	  printf("%d ",ans[i]);
    }
    */
    //==========可能负优化的LLL+SLF优化================
    /*
    #include<bits/stdc++.h>
    #define mokou 2147483647
    using namespace std;
    struct edg
    {
        int u,v,w,next;
    }asd[5000550];
    int sum,n,m,s,vis[150000],sp[150000],head[150000];
    void put(int a,int b,int c)
    {
        asd[++ sum].u = a;
        asd[sum].v = b;
        asd[sum].w = c;
        asd[sum].next = head[a];
        head[a] = sum;
    }
    deque<int>q;
    int main(){
        int cnt = 1,tot = 0;
        cin >> n >> m >> s;
        for(int i = 1;i <= m;i ++)
    	{
            int u,v,w;
            cin >> u >> v >> w;
            put(u,v,w);
        }
        for(int i = 1;i <= n;i ++)
            sp[i] = mokou;
        sp[s] = 0;
        vis[s] = 1;
        q.push_front(s);
        while(! q.empty())
          {
            int x = q.front();
            while(cnt * sp[x] > tot)                                             
              {
                q.pop_front();
                q.push_back(x);
                x = q.front();
              }
            q.pop_front();
            cnt --, tot -= sp[x], vis[x] = false;                                     
            for(int i = head[x]; i; i = asd[i].next)
                if(sp[asd[i].v] > sp[x] + asd[i].w)
                  {
                    sp[asd[i].v] = sp[x] + asd[i].w;
                    if(! vis[asd[i].v])
                      {
                        vis[asd[i].v] = 1;
                        if(q.empty() || sp[asd[i].v] > sp[q.front()])   
                            q.push_back(asd[i].v);
                        else                                        
                            q.push_front(asd[i].v);
                        cnt++, tot += sp[asd[i].v];                                 
                      }
                  }
          }
        for(int i = 1;i <= n;i ++)
    	  cout << sp[i] << " ";
    }
    */ 
    

    有什么问题可以在下方评论留言或找我私聊哦
    (QQ:2876140034)
    (Luogu:Soroak)

  • 相关阅读:
    洛谷题单 算法1-1 模拟和高精度
    第十一届蓝桥杯 b组
    jdk的安装、java环境配置
    哈夫曼树 java
    机器学习基石8-Noise and Error
    Java基础12-工具类;变长参数;IO
    机器学习基石7-The VC Dimension
    Java基础11-List;Set;Map
    Java基础10-集合
    机器学习基石6-泛化理论
  • 原文地址:https://www.cnblogs.com/Soroak/p/11695391.html
Copyright © 2020-2023  润新知