【图论入门】最短路

最短路算法框架

最短路问题的求解主流有五种算法,分别适用不同的情况

1.朴素Dijkstra算法

2.堆优化版Dijkstra算法

3.Bellman-Ford算法

4.SPFA算法

5.Floyd算法

单源最短路: 求一个点到其他点的最短路

多源最短路: 求任意两个点的最短路

稠密图: m 和 n^2 一个级别

稀疏图: m 和 n 一个级别

稠密图用邻接矩阵存,稀疏图用邻接表存

朴素Dijkstra算法

算法性质

适用于单源最短路径问题。

适用于稠密图,使用邻接矩阵存储图

时间复杂度为O(n^2),对于稀疏图可能较慢。

不能处理负权边,但可以处理非负权图

使用贪心思想,每次选择当前未访问的距离起点最近的点,逐步构建最短路径。

实现步骤

初始化距离数组,将起点到自身的距离设置为0,其他点到起点的距离设置为无穷大。

循环n次,每次选择当前未访问的点中距离起点最近的点,并标记为已访问。

通过新加入的点更新其他点的最短距离。

模板代码

#include<bits/stdc++.h>
using namespace std;

struct point {
    int to, cost;
};
vector<point> g[100010];  // 邻接矩阵 存图 
int dis[100010];  // dis[i] 表示从起点s到终点i的最短长度 
bool vis[100010]; // 记录点是否访问过
int n, m, s; // 点数,边数,起点

void dijkstra()
{
    dis[s] = 0; // 起点到自己的距离为0
    for (int i = 1; i <= n; i++)
    {
        int t = -1;
        for (int j = 1; j <= n; j++)
        {
            if (!vis[j] && (t == -1 || dis[j] < dis[t])) {
                t = j;
            }    
        }
        vis[t] = true; // 将最小距离的点标记为已访问
        for (int j = 1; j <= n; j++) {
            dis[j] = min(dis[j], dis[t] + g[t][j].cost); // 通过t更新其他点的距离
        }   
    }
}

int main()
{
    cin >> n >> m >> s; // 读取点数,边数,起点
    memset(dis, 0x3f, sizeof(dis)); // 初始化距离数组为无穷大
    while (m--)
    {
        int u, v, d;
        cin >> u >> v >> d; // 读取边的起点,终点和权值
        g[u].push_back({ v,d }); // 添加边的信息到邻接矩阵
    }
    dijkstra(); // 调用朴素Dijkstra算法求解最短路径
    for (int i = 1; i <= n; i++) {
        cout << dis[i] << " "; // 输出从起点到每个点的最短距离
    }
    return 0;
}

堆优化版Dijkstra算法

算法性质

适用于单源最短路径问题。

适用于稀疏图,使用邻接表存储图

时间复杂度为O(mlogn),在边数相对点数较少时较快。

不能处理负权边,但可以处理非负权图

通过使用最小堆优化朴素 Dijkstra 算法,提高了贪心地寻找最小距离点的效率,减少了不必要的比较。

实现步骤

初始化距离数组,将起点到自身的距离设置为0,其他点到起点的距离设置为无穷大。

建立一个最小堆,将起点入队,并不断从堆中取出距离起点最近的点。

通过新加入的点更新其他点的最短距离。

模板代码

#include<bits/stdc++.h>
using namespace std;

typedef pair<int,int> node;
struct point{
	int to,cost;
};
vector<point> g[100010];//邻接矩阵 存图 
int dis[100010];//dis[i] 从起点s 到终点i 的最短长度 
bool vis[100010];//是否访问 
int n,m,s;

void dijkstra()
{
	priority_queue<node,vector<node>,greater<node> > q;
    dis[s] = 0;
    q.push({0,s});
    while(!q.empty())
    {
        node tmp = q.top();
        q.pop();
        int x = tmp.second;
		int d = tmp.first;
        if(vis[x]){
			continue;
		}else{
			vis[x] = 1;
		} 
        for(int i=0;i<g[x].size();i++){
        	int y = g[x][i].to;
        	int newDis = d + g[x][i].cost;
            if(dis[y] > newDis)
            {
            	dis[y] = newDis;
                if(!vis[y])
                {
                    q.push({newDis, y} );
                }
            }        	
        }
    }
}
int main()
{
    cin>>n>>m>>s; 
    memset(dis,0x3f3f3f3f,sizeof dis); 
    for(int i=0;i<m;i++)
    {
        int u, v, d;
        cin>>u>>v>>d;
        g[u].push_back({v,d});
    }
    dijkstra();
    for(int i=1;i<=n;i++){
    	cout<<dis[i]<<" "; 
    }
    return 0;
}

Bellman-Ford算法

算法性质

适用于单源最短路问题,可处理带负权边的图。

时间复杂度为O(nm),效率较低。

可检测负权回路,用于判断图是否存在负权回路

采用动态规划思想,通过不断更新点之间的最短距离,逐步求解出整个图的最短路径。

实现步骤

初始化距离数组,将起点到自身的距离设置为0,其他点到起点的距离设置为无穷大。

循环n-1次,对所有边进行松弛操作,即通过已知的边更新到达其他点的最短距离。

循环n次,检查是否存在负权回路

模板代码

#include <bits/stdc++.h>
#define INF 2147483647
using namespace std;
struct node{
	long long from,to,cost;
}es[1000010<<1];
long long d[1000010<<1];
long long n,m,s;//点,边,起 

void bellman_ford_getShortest_Path(long long s){
	//初始化 
	for(int i=1;i<=n;i++)d[i] = INF;
	d[s] = 0;
	//开始更新 
	while(true){
		bool update = false;
		for(int i=1;i<=m;i++){
			node e = es[i];
			//如果发现  d[e.from] 的距离更新了
			//并且 从 e.from 到 e.to 的距离更短
			//则更新 d[e.to]
			if(d[e.from] != INF && d[e.to] > d[e.from] + e.cost){
				d[e.to] = d[e.from]+e.cost;
				update = true;
			}
		}
		if(!update)break;
	} 
}
int main() {
	//读图 
	cin>>n>>m>>s;
	for(int i=1;i<=m;i++){
		long long u,v,w;
		cin>>u>>v>>w;
		es[i] = {u,v,w};
	}
	//开始计算最短路 
	bellman_ford_getShortest_Path(s);
	//输出从起点开始到第0~n个点的最短距离 
	for(int i=1;i<=n;i++){
		cout<<d[i]<<" ";
	}
}

SPFA算法

算法性质

适用于单源最短路问题,可处理带负权边的图

队列优化版的Bellman-Ford算法,常常比Bellman-Ford快,但可能在存在负权回路的情况下陷入死循环

平均时间复杂度取决于实际图的特点,一般情况下优于Bellman-Ford

时间复杂度 最坏 O(nm) 容易被卡!!!它已经死辣!

SPFA算法通过队列只对当前最近的点进行松弛操作,减少了不必要的计算。

实现步骤

初始化距离数组,将起点到自身的距离设置为0,其他点到起点的距离设置为无穷大。

建立一个队列,将起点入队,并不断从队列中取出点。

通过新加入的点更新其他点的最短距离。

模板代码

#include <bits/stdc++.h>
using namespace std;

int n,m,s;
struct point{
	int to,cost;
};
int vis[100010];
int dis[100010];
vector<point> g[100010];

void spfa(){
	queue<int> q;
	memset(dis, 0x3f, sizeof dis);
	dis[s] = 0;
	vis[s] = true;
	q.push(s);
	while(!q.empty()){
		int u = q.front();
		q.pop();
		vis[u] = false;
		for(int i=0;i<g[u].size();i++){
			if(dis[g[u][i].to]>dis[u]+g[u][i].cost){
				dis[g[u][i].to] = dis[u]+g[u][i].cost;
				if(!vis[g[u][i].to]){
					q.push(g[u][i].to);
					vis[g[u][i].to] = true;
				}
			}
		}
	}
	
}
int main(){
	//读图 
	cin>>n>>m>>s;
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		g[u].push_back({v,w});
	}
    spfa();
	for(int i=1;i<=n;i++){
		cout<<dis[i]<<" ";
	}
    return 0;
}

Floyd算法

算法性质

适用于多源最短路问题,求任意两个点之间的最短路径。

时间复杂度为O(n^3),适合处理点数较少的图。

适合解决稠密图中的最短路径问题。

可处理带负权边的图但不能存在负权回路

使用动态规划思想,通过不断利用中转点更新任意两点之间的最短距离,逐步求解出整个图的最短路径。

实现步骤

初始化距离数组,将点之间的距离初始化为边的权值,自己到自己的距离为0。

通过中转点遍历更新任意两点之间的最短距离。

模板代码

#include <bits/stdc++.h>
using namespace std;

int n, m;
int d[210][210];

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]);
    		}
    	}
    }    
}

int main() {
	//Read In
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
    	for(int j = 1; j <= n; j++){
            if(i == j){
            	d[i][j] = 0;
            }else{
            	d[i][j] = 0x3f3f3f3f; 
            }
             		
    	}
    }
    for(int i=1;i<=m;i++){
    	int x,y,z;
        cin >> x >> y >> z;
        d[x][y] = d[y][x] = min(d[x][y], z);    	
    }
    floyd();
    for(int i = 1; i <= n; i++){
    	for(int j = 1; j <= n; j++){
			cout<<d[i][j]<<" ";
		}
		cout<<endl;
	}    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值