推荐博客

Image

朴素Dijkstra

时间复杂度为O(n^2)
核心代码:

for(int i=0; i<n; i++)
	{
		int t = -1;
		for(int j=1; j<=n; j++)   // 在没有确定最短路中的所有点找出距离最短的那个点 t 
		   if(!st[j] && (t == -1 || dist[t] > dist[j]))
		    t=j;                  
		    
		st[t]=true; // 代表 t 这个点已经确定最短路了
		
		for(int j=1; j<=n; j++) // 用 t 更新其他点的最短距离 
		 dist[j] = min(dist[j],dist[t]+g[t][j]);
	 } 

堆优化Dijkstra

时间复杂度O(nlogn+m)
模板:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef pair<int,int> PII; //first存距离,second存结点编号 
const int N = 2e5+10;
int n,m;
int h[N],w[N],e[N],ne[N],idx; // 邻接表的存储 
int dist[N]; // 第 i 个点到起点的最短距离
bool st[N]; // 代表第 i 个点的最短路是否确定、是否需要更新 

void add(int a,int b,int c)
{
	e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}

int dijkstra()
{
	memset(dist,0x3f,sizeof(dist)); // 将所有距离初始化正无穷
	dist[1] = 0;  // 第一个点到起点的距离为 0  
	
	priority_queue<PII, vector<PII>, greater<PII>> heap; // 小根堆
	heap.push({0,1}); //把 1 号点放入堆中 
	
	while(heap.size()) // 堆不空
	{
	   	PII t = heap.top(); //找到当前距离最小的点
		heap.pop();
		
		int ver = t.second,distance = t.first; // ver为编号,distance为距离
		if(st[ver]) continue;   // 重边的话不用更新其他点了
		st[ver] = true;   //标记 t 已经确定最短路
		
		for(int i = h[ver]; i!=-1; i=ne[i])  // 用 t 更新其他点的最短距离
		{
			int j = e[i];
			if(dist[j] > distance + w[i])
			{
				dist[j] = distance + w[i];
				heap.push({dist[j],j}); //入堆
			}
		}
	}  
	 
	if(dist[n] == 0x3f3f3f3f) return -1;  // 说明 1 和 n 是不连通的,不存在最短路 
	return dist[n];
}
int main()
{
	memset(h,-1,sizeof(h));
	cin >> n >> m;
	while(m--)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
	}
	cout << dijkstra(); 
	return 0;
}

Bellman-Ford

时间复杂度O(nm)
模板:

#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 510,M = 10010;

int n,m,k;
int dist[N],backup[N]; //backup数组为上次
struct edges
{
   	int a,b,w; // a->b权值为w的边 
}edge[M];

int bellman_ford()
{
	memset(dist,0x3f,sizeof(dist));
	dist[1] = 0;
	
	for(int i=0; i<k; i++) 
	{
		memcpy(backup,dist,sizeof(dist)); //备份
		for(int j=0; j<m; j++)   // 枚举所有边 
		{
		   int a = edge[j].a, b = edge[j].b, w=edge[j].w;	
		   dist[b] = min(dist[b],backup[a]+w); // 用备份更新 
		}
	}
	if(dist[n] > 0x3f3f3f3f/2) return -1;
	return dist[n];
}
int main()
{
    cin >> n >> m >> k;
	for(int i=0; i<m; i++)
	{
	   int a,b,w;
	   cin >> a >> b >> w;
	   edge[i] = {a,b,w}; 	
	}	
	int t = bellman_ford();
	
	if(t == -1) cout << "impossible";
	else cout << t;
	return 0;
} 

SPFA

是对Bellman-Ford的优化,但是最坏情况下依然是O(nm)的时间复杂度。
模板:

int spfa()
{// bool st[N]: 存第 i 个点是不是在队列中,防止存重复的点 
	memset(dist,0x3f,sizeof(dist));
	dist[1] = 0;
	
	queue<int> q; //存储所有待更新的点
	q.push(1);  // 1号点入队 
	st[1] = true;
	while(q.size()) // 队列不空
	{
	   int t = q.front(); //取队头 
	   q.pop();
	   st[t] = false; // 代表这个点已经不在队列了,因为存在边权为负数,某个点可能会被更新多次,所以可以多次入队和出队。
	   
	   for(int i = h[t]; i!=-1; i=ne[i]) // 更新 t 的所有临边结点的最短路 
	   {
	   	 int j = e[i];
	   	 if(dist[j] > dist[t]+w[i])
	   	 {
	   	    dist[j] = dist[t] + w[i];
			if(!st[j])  //如果 j 不在队列,让 j 入队 
			{
				q.push(j); 
				st[j] = true;  // 标记 j 在队中 
			} 	    	
		 }
	   }	
	} 
	 if(dist[n] == 0x3f3f3f3f) return -1; // 不存在最短路 
	 return dist[n]; 
}

SPFA判断负环

用cnt数组记录每个节点最短路径的长度。

int spfa()
{
	queue<int> q; 
    for(int i=1; i<=n; i++) //将所有结点入队
	{
	    st[i] = true;
		q.push(i);	  
    }
	while(q.size()) // 队列不空
	{
	   int t = q.front(); //取队头 
	   q.pop();
	   st[t] = false; // 代表这个点已经不在队列了
	   
	   for(int i = h[t]; i!=-1; i=ne[i]) // 更新 t 的所有临边结点的最短路 
	   {
	   	 int j = e[i];
	   	 if(dist[j] > dist[t]+w[i])
	   	 {
	   	    dist[j] = dist[t] + w[i];
	   	    cnt[j] = cnt[t] + 1; // t到起点的边数+1 
	   	    
	   	    if(cnt[j] >= n) return true;// 存在负环 
			if(!st[j])  //如果 j 不在队列,让 j 入队 
			{
				q.push(j); 
				st[j] = true;  // 标记 j 在队中 
			} 	    	
		 }
	   }	
	} 
	 return false;// 不存在负环 
}

Floyd

动态规划思想,正确性证明有点复杂,暂时不理会。

void floyd()
{
 for(int k=1; k<=n; k++)
   for(int i=1; i<=n; i++)
     for(int j=1; j<=n; j++)
      d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}