最短路径

在这里插入图片描述

单源最短路径

单源最短路:求一个点到其他所有点的最短距离。

所有边权都是正数

所有边权都是正数:所有边的权重都是正数。

朴素Dijkstra算法

朴素Dijkstra算法的时间复杂度为O(n²)(n表示点数),与边没有关系,所以我们多用朴素Dijkstra算法来解决稠密图的问题。
实现过程
1.初始化距离。

  • dist[1] = 0;dist[i] = +oo;

2.迭代循环n次,确定到每个点的最短距离。

  • 先假设一个数组st[]用来储存当前已确定最短距离的点,t用来存不在st[]中的当前最短距离的点。
  • 再循环得到最短距离后,将t存入st[]中。
  • 最后用t来更新其他点的距离。
    图示
    在这里插入图片描述
    在这里插入图片描述

代码模板

int dijkstra()
{
	memset(dist, 0x3f,sizeof dist);//初始化距离 
	dist[1] = 0;//dist[]表示各个点到起点的距离 
	for(int i = 0; i < n; i ++)//迭代循环n次  
	{
		int t = -1;
		for(int j = 1; j <= n; j ++) //寻找最短距离 ,st[]判断该点是否已经是最短路径 
		{
			if(!st[j] && (t = -1 || dist[t] > dist[j]))// dist[t] > dist[j]用来判断当前的路径是否是最短的 
			t = j;
		}
		st[t] = true;//该点已经确定最短距离 
		for(int j = 1; j <= n; j ++)//用t来更新其他点(这里的其他点是指从t点出发,能直达的点)到起点的距离 
		{
			dist[j] = min(dist[j],dist[t] + g[t][j]);//g[t][j]表示t点到j点的距离
		}
	}
		if(dist[n] == 0x3f3f3f3f)//输出到n点的最短距离 (当然输出其他点也可以)
		return -1;
		else
		return dist[n];
}

堆优化版的Dijkstra算法

堆优化版的Dijkstra算法的时间复杂度为O(mlog(n))(n表示点数,m表示边数),多用来解决稀疏图的问题。
实现操作
在朴素Dijkstra算法的基础上,用优先队列优化循环过程。
代码模板

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;

typedef pair<int,int> PII;//用邻接表储存所有边 
 
const int N = ... ;

...
int h[N], e[N], ne[N], w[N], idx;//h[]用来储存点对应的下标,w[]用来储存权重 
int dist[N];//dist[]用来储存点到起点的距离 
bool st[N]; //判断该点是否已经确定最短距离 
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;
	priority_queue<PII, vector<PII>, greater<PII>> heap;//优先队列初始化是大根堆,所以我们要用greater函数,让它变成小根堆
	//<类型,<储存方式>,<比较函数> > 
	heap.push({0,1});//first储存距离,second储存编号
	//pair 定义了自己的排序规则:先比较第一维,相等了再比较第二维因此储存时需要按照{dist[i],i},而不是{i,dist[i]}的方式组合
	while(heap.size())
	{
		auto t = heap.top(); //取出队首元素并且弹出队列
		heap.pop();
		
		int a = t.second, b = t.first;//a表示从1~这个点,b表示路径长度
		if(st[a] == true) continue;//如果是确定的最短路则直接进行下一循环
		st[a] = true;
		
		for(int i = h[a]; i != -1; i = ne[i])//h[]表示当前结点的下标 
		{
			int j = e[i];//此时i代表当前节点的开始位置,j代表节点的结束位置,w[i]指的是路径距离 
			if(dist[j] > dist[a] + w[i])//更新dist[]数组
			{
				dist[j] = dist[a] + w[i];
				heap.push({dist[j], j}); 
			}
		}
	}
	if(dist[n] == 0x3f3f3f3f)
	    return -1;
	else
	    return dist[n];    
}

int main()
{
	...
	... 
	memset(h, -1, sizeof h);//h[]初始化都为-1 
	int a, b, c;
	cin >> a >> b >> c;
	add();
	...
	...

	...   dijkstra();
	return 0;
}

存在负权边

存在负权边:某些边的权重存在负值。

Bellman-Ford算法

Bellman-Ford算法时间复杂度为O(nm)

int n, m;       // n表示点数,m表示边数
int dist[N];        // dist[x]存储1到x的最短路距离

struct Edge     // 边,a表示出点,b表示入点,w表示边的权重
{
    int a, b, w;
}edges[M];

// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
int bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    // 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
    for (int i = 0; i < n; i ++ ) //进行n次松弛(迭代)
    {
        for (int j = 0; j < m; j ++ )
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            dist[b] = min(dist[b],dist[a] + w);
        }
    }

    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    return dist[n];
}

SPFA算法

SPFA算法是Bellman-Ford算法的优化,其的时间复杂度一般为O(m),最坏的情况的话为O(nm)
AcWing 851. spfa求最短路

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>

using namespace std;

typedef pair<int,int> PII;

const int N = 2e5;

int n,m;      // 点的数量
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储所有点到1号点的距离
bool st[N];     // 存储每个点的最短距离是否已确定


void add(int a, int b, int c) //邻接表添加节点
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    
    queue<int> q;
    q.push(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])
        {
            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;
                }
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}
int main()
{
    cin >> n >> m;
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b,c;
        cin >> a >> b >> c;
        add(a,b,c);
    }
    cout << spfa();
    return 0;
}

多源汇最短路

多源汇最短路:起点和终点都不确定,求从某一起点到某一终点的最短路距离。

Floyd算法

Floyd算法的时间复杂度为O(n³)。

初始化:
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            d[i][j] = i == j ? 0 : INF;

// 算法结束后,d[a][b]表示a到b的最短距离
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]);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值