zoukankan      html  css  js  c++  java
  • 最短路径


    没有用的话qaq :
    Ummmm…图论的大部分知识本来早就有学过,只是一直没有写成博文来梳理,但既然上了qbxt DP图论就写一篇来总结下,主要是来听DP的,但…由于太菜的原因,DP听得天花乱坠QWQ


    图中的最短路算法分为单源最短路算法(dijkstra,Bellman-food,spfa)和多源最短路算法(floyd),单源最短路算法是求图上一点到其他任意一点的最短路径,多源最短路算法是求图上任意两点之间的最短路。
    一,多源最短路算法
    Floyd弗洛伊德算法,运用了动态规划的思想,用邻接矩阵存储,期初将从任何一点到其他所有点的最短路都置成正无穷,然后输入时修改有的权值,其主要思想是动态规划,在最外围枚举点,看看有没有一个点可以更新两点之间的最短路。

    代码简单易懂

    memset(dis,0x3f,sizeof(dis));
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
             for(int j=1;j<=n;j++)
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    

    时间复杂度为O(n^3 ),空间复杂度为O(n^2),时间和空间均不是很优,一般较少采用,一般在求n范围较小的多源最短路题中才有出现。
    可以适用于有负权边的情况,可以判断图的连通性,可以传递闭包


    二,单源最短路算法
    1,Dijkstra
    a.将所有点分为两个集合,最短路确定的集合S 和最短路未确定的集合T,初始S ={s}
    b.求T 中每个点v 的当前最短路dv = min<u,v>∈E,u∈S{du + wu,v}
    c.取出T 中dv 最小的点,其最短路一定就是dv,将其加入S
    d.不断重复上面的操作,直到所有点的最短路都确定

    与普利姆的思想基本相同,代码实现也基本相同,同样朴素写法时间复杂度较劣,可以采用堆优化至O((N + M)logN)

    inline void dijsktra(int s)
    {
    	for(int i=1;i<=n;i++)
    	    dist[i]=INF,vis[i]=false;
    	heap.push(make_pair(0,s));
    	vis[s]=true;
    	while(!heap.empty())
    	{
    		int p=heap.top().second; 
    		heap.pop();
    		if(vis[p]) continue;
    		vis[p]=true;
    		for(int i=first[p];i;i=edge[i].nxt)
    		{
    			int e=edge[i].ed,d=edge[i].len;
    			if(dist[e]>dist[p]+d)
    			{
    				dist[e]=dist[p]+d;
    				heap.push(make_pair(-dist[e],e));
    			}
    		}
    	}
    }
    

    Dijikstra算法是图论最短路算法中最高效的算法,但注意其不适用于含有负权边的情况。


    3,Bellmanfood,SPFA

    其实是一个东西啦,SPFA是对Bellfood的优化,所以在这里就只整理SPFA了,比起Dijkstra,SPFA的用途更为广泛,其适用于有负权边的情况,可以判断一个图里是否有负环,但硬伤是效率,极容易被卡。
    我们先看Bellmanfood的算法流程:
    a.初始令ds = 0,其余di =∞
    b. 依次使用每一条边< u, v > 更新,dv  min{dv , du + wu,v}
    c.不断循环直到所有di 都不会更新
    d.因为任何一条最短路包含至多n − 1 条边,没有负环则不超过n 轮就会停止
    SPFA考虑使用队列优化Bellman-Ford 算法,如果更新了du,则将u入队。每次取队首u 更新其邻点v 的dv。因为Bellmanfood会遍历一些不可能完成松弛操作的情况。

    代码

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    
    using namespace std;
    
    struct node
    {
    	int ed,len,nxt;
    };
    node edge[2333];
    int n,m,first[2333],dis[2333],cnt;
    bool vis[2333];
    
    queue <int> q;
    
    inline void add_edge(int s,int e,int d)
    {
    	cnt++;
    	edge[cnt].ed=e;
    	edge[cnt].len=d;
    	edge[cnt].nxt=first[s];
    	first[s]=cnt;
    }
    
    inline void spfa(int st)
    {
    	memset(dis,0x3f,sizeof(dis));
    	memset(vis,false,sizeof(dis));
    	q.push(st); vis[st]=true;
    	while(!q.empty())
    	{
    		int p=q.front(); vis[p]=false;
    		for(int i=first[p];i;i=edge[i].nxt)
    		{
    			int e=edge[i].ed;
    			int d=edge[i].len;
    			int newd=dis[p]+d;
    			if(newd<dis[e])
    			{
    				dis[e]=newd;
    				if(!vis[e])
    				{
    					q.push(e);
    					vis[e]=true;
    				}
    			}
    		}
    	}
    	return;
    }
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;i++)
    	{
    		int s,e,d;
    		scanf("%d%d%d",&s,&e,&d);
    		add_edge(s,e,d);
    		add_edge(e,s,d);
    	}
    	spfa(1);
    	return 0;
    }//随手一写,不保证编译能过 Orz QwQ
    

    SPFA判负环,就要有一个点的进队次数大于等于N,那么我们就认为有负环,因为负环会产生最短路为无限小,因此它会在那不停的转,有时候对于比较稠密的图,可能会卡SPFA判负环,所以对于比较稠密的图把队列换成栈可以更快的找出负环。

    队列实现代码

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    
    using namespace std;
    
    struct node
    {
    	int ed,len,nxt;
    };
    node edge[2333];
    int n,m,first[2333],inq[2333],dis[2333],cnt;
    bool vis[2333];
    
    queue <int> q;
    
    inline void add_edge(int s,int e,int d)
    {
    	cnt++;
    	edge[cnt].ed=e;
    	edge[cnt].len=d;
    	edge[cnt].nxt=first[s];
    	first[s]=cnt;
    }
    
    inline bool spfa(int st)
    {
    	memset(dis,0x3f,sizeof(dis));
    	memset(vis,false,sizeof(dis));
    	q.push(st); vis[st]=true; inq[st]++;
    	while(!q.empty())
    	{
    		int p=q.front(); vis[p]=false;
    		for(int i=first[p];i;i=edge[i].nxt)
    		{
    			int e=edge[i].ed;
    			int d=edge[i].len;
    			int newd=dis[p]+d;
    			if(newd<dis[e])
    			{
    				dis[e]=newd;
    				if(!vis[e])
    				{
    					q.push(e);
    					inq[e]++;
    					vis[e]=true;
    					if(inq[e]>=n) return true;
    				}
    			}
    		}
    	}
    	return false;
    }
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=m;i++)
    	{
    		int s,e,d;
    		scanf("%d%d%d",&s,&e,&d);
    		add_edge(s,e,d);
    		add_edge(e,s,d);
    	}
    	spfa(1);
    	return 0;
    }
    

    写在最后,Yousiki Orz Orz

  • 相关阅读:
    线程池全面总结
    有状态(Stateful)与无状态(Stateless)
    Callable接口--有返回值的线程
    集合类--最详细的面试宝典--看这篇就够用了(java 1.8)
    [Android App]IFCTT,即:If Copy Then That,一个基于IFTTT的"This"实现
    应朋友死皮白咧地邀请贴一个招聘广告
    [Android]Android焦点流程代码分析
    [Android]Android内存泄漏你所要知道的一切(翻译)
    [Android]Gradle 插件 DiscardFilePlugin(class注入&清空类和方法)
    [Android]使用RecyclerView替代ListView(四:SeizeRecyclerView)
  • 原文地址:https://www.cnblogs.com/Hoyoak/p/11336583.html
Copyright © 2011-2022 走看看