图论最短路总结

本文介绍了三种经典最短路径算法:Dijkstra算法的朴素实现和堆优化版,Floyd-Warshall的多源最短路,以及SPFA的单源最短路径,展示了它们的原理、代码实现及优化技巧。特别强调了SPFA在面对负权边时的特性。
摘要由CSDN通过智能技术生成

几种最短路算法模板

1.Dijkstra

从起点开始,每次选取距离起点最近且未被访问过的点,更新最短路径,直到所有点到起点的最短路径确认

朴素dijkstra,时间复杂度 O(n^2):

//Dijkstra算法
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<string>
#define Inf 0x3f3f3f3f//极大值
#define maxn 1010
using namespace std;

int n, m;//点,边
int start, goal;//起点,终点
int map[maxn][maxn];//图
int vis[maxn], dis[maxn];//是否访问,距离

void init()
{
	for (int i = 1; i <= n; i++)
		dis[i] = map[start][i];
}

void dijkstra()
{
	init();
	int minn;//记录每趟最小路径值
	int pos;//记录最短路下标
	vis[start] = 1;
	for (int i = 1; i <= n; i++)
	{
		minn = Inf;
		for (int j = 1; j <= n; j++)//找到相连最小边权点,为第pos个点
		{
			if (!vis[j] && dis[j] < minn)
			{
				minn = dis[j];
				pos = j;
			}
		}
		vis[pos] = 1;

		for (int j = 1; j <= n; j++)//更新距离
		{
			if (!vis[j] && dis[j] > dis[pos] + map[pos][j])
				dis[j] = dis[pos] + map[pos][j];
		}
	}
}

int main()
{
	cin >> n >> m;
	cin >> start >> goal;
	memset(map, Inf, sizeof(map));
	int u, v, w;
	for (int i = 1; i <= m; i++)
	{
		cin >> u >> v >> w;
		map[u][v] = w;
		map[v][u] = w;
	}
	dijkstra();
	cout << dis[goal] << endl;
}

为了达到更高的效率

堆优化后的dijkstra,时间复杂度 O(m*logn):

//Dijkstra_Optimized算法
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<string>
#define Inf 0x3f3f3f3f//极大值
#define maxn 10010
using namespace std;

struct edge
{
    int to;//终点
    int dis;//权值
    int next;//下一条边
    //不添加from起点,意义不大
};
struct Node
{
    int dis;//权值
    int pos;//下标
    bool operator <(const Node& x)const
    {
        return x.dis < dis;//从小到大排序
    }
};
edge e[maxn];
int head[maxn], dis[maxn], cnt, vis[maxn];
int n, m, s;
priority_queue<Node>q;//每次出队的都是dis最小的,节省了(更新后再比,出现更小的再更新此类情况)消耗的时间 
void addedge(int u, int v, int w)
{
    cnt++;
    e[cnt].dis = w;
    e[cnt].to = v;
    e[cnt].next = head[u];//head[u]总是临时存放着同起点的前一条边,当同起点的后一条边输入时,把前一条边的index赋值给这条边的next(于是同起点的边顺序是输入顺序的反序)
    head[u] = cnt;
}
void Dijkstra()
{
    fill(dis, dis + n + 1, Inf);
    dis[s] = 0;
    q.push({ 0, s });//加入起点
    while (!q.empty())
    {
        Node tmp = q.top();
        q.pop();
        int x = tmp.pos, d = tmp.dis;
        if (!vis[x])continue;
        vis[x] = 1;
        for (int i = head[x]; i; i = e[i].next)
        {
            int y = e[i].to;
            if (dis[y] > dis[x] + e[i].dis)
            {
                dis[y] = dis[x] + e[i].dis;
                q.push({ dis[y], y });
            }
        }
    }
}

int main()
{
    cin >> n >> m >> s;
    for (int i = 1; i <= m; i++)
    {
        int u, v, w;
        cin >> u >> v >> w;
        addedge(u, v, w);
    }
    Dijkstra();
    for (int i = 1; i <= n; i++)
        cout << dis[i] << ' ';
}

2.Floyed

对于一个图map,遍历每一个节点作为中间节点k,对于每一个节点k,比较map[i][k]+map[k][j]和map[i][j]的较小值,更新map[i][j]为较小值,这样map[i][j]就成为任意两点的最短路

可以求多源最短路,时间复杂度 O(n^3):

//Floyd算法
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<string>
#define Inf 0x3f3f3f3f//极大值
#define maxn 1010
using namespace std;

int n, m;//点,边
int start, goal;//起点,终点
int map[maxn][maxn];//图

void floyd()
{
	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])
					map[i][j] = map[i][k] + map[k][j];
}


int main()
{
	cin >> n >> m;
	cin >> start >> goal;
	memset(map, Inf, sizeof(map));
	for (int i = 1; i <= n; i++)
		map[i][i] = 0;
	int u, v, w;
	for (int i = 1; i <= m; i++)
	{
		cin >> u >> v >> w;
		map[u][v] = w;
		map[v][u] = w;
	}
	floyd();
	cout << map[start][goal]<<endl;
}

3.SPFA

SPFA是Bellman-Ford的队列优化,思想同BFS

可以判断负权边,时间复杂度 O(m)~  O(n*m)

SPFA矩阵实现:

//SPFA算法
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<string>
#define Inf 0x3f3f3f3f//极大值
#define maxn 2510
using namespace std;

int n, m;//点,边
int start, goal;//起点,终点
int map[maxn][maxn];//图
int vis[maxn],dis[maxn];//是否在队中,距离
int cnt[maxn];//记录顶点入队次数

void init()
{
	memset(dis, Inf, sizeof(dis));
	memset(map, Inf, sizeof(map));
}

bool spfa()
{
	queue<int>q;
	dis[start] = 0;
	q.push(start);

	cnt[start]++;
	vis[start] = 1;

	while (!q.empty())
	{
		int temp = q.front();
		q.pop();
		vis[temp] = 0;

		for (int i = 1; i <= n; i++)
		{
			if (dis[i] > dis[temp] + map[temp][i])
			{
				dis[i] = dis[temp] + map[temp][i];
				if (!vis[i])
				{
					q.push(i);
					cnt[i]++;
					vis[i] = 1;
					if (cnt[i] > n)
						return 0;
				}
			}
		}
	}
	return 1;
}

int main()
{
	cin >> n >> m;
	cin >> start >> goal;
	init();
	int u, v, w;
	for (int i = 1; i <= m; i++)
	{
		cin >> u >> v >> w;
		map[u][v] = w;
		map[v][u] = w;
	}
	int result = spfa();
	if (!result)
		cout << "存在负环" << endl;
	else
		cout << dis[goal] << endl;
}

SPFA邻接表实现(推荐):

//SPFA算法
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<string>
#define Inf 0x3f3f3f3f//极大值
#define maxn 10100
using namespace std;

int n, m;//点,边
int start, goal;//起点,终点
int vis[maxn],dis[maxn];//是否在队中,距离
int cnt[maxn];//记录顶点入队次数

typedef struct Edge {
	int to;//边的终端节点
	int w;//边权值。
	Edge(int to, int w) :to(to), w(w) {}//构造函数
}Edge;
vector<Edge> graph[maxn];//用vector模拟邻接表

void init() {
	memset(cnt, 0, sizeof(cnt));
	memset(dis, Inf, sizeof(dis));
	memset(vis, false, sizeof(vis));
}
bool spfa() 
{
	queue<int> q;
	dis[start] = 0;
	q.push(start);

	cnt[start]++;
	vis[start] = 1; 

	while (!q.empty())
	{
		int temp = q.front();
		q.pop();
		vis[temp] = 0; //出队则false。
		int v, w;
		int t = graph[temp].size();//避免多次调用此函数。
		//松弛操作
		for (int i = 0; i < t; i++)
		{
			v = graph[temp][i].to;
			w = graph[temp][i].w;
			if (dis[v] > dis[temp] + w) 
			{
				dis[v] = dis[temp] + w;//更新最短路径
				if (!vis[v])
				{
					q.push(v);
					cnt[v]++;
					vis[v] = 1;
					if (cnt[v] > n) { return 0; }
				}
			}
		}
	}
	return 1;
}

int main() 
{
	int u, v, w;
	init();
	cin >> n >> m;
	cin >> start >> goal;
	for (int i = 0; i < m; i++) 
	{
		cin >> u >> v >> w;
		graph[u].push_back(Edge(v, w));
	}
	int result = spfa();
	if (!result)
		cout << "存在负环" << endl;
	else
		cout << dis[goal] << endl;
	return 0;
}	

注意事项

由于SPFA时间复杂度不稳定,可能在实际解题中过不去,如洛谷P4779,所以在解决单源最短路时,比赛者似乎习惯用更稳定的堆优化Dijkstra解题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值