关于最短路


[前言&胡扯]

\(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\)

posted @ 2019-10-17 21:56  Soroak  阅读(191)  评论(5编辑  收藏  举报