最短路问题模版总结

目录

思维导图

Dijkstra(朴素)

思路:

代码如下:

Dijkstra(堆优化)

代码如下:

Bellman-Ford

思路:

对于串联效应的解释:(也就是为什么需要备份数组)

代码如下:

SPFA

思路:

为什么和BF算法的判断不一样:

代码如下: 

SPFA判负环

思路:

代码如下:

Floyd

​编辑思路:

代码如下: 

复习小结~~


符号:n为点数,m为边数

思维导图

最短路问题模版总结_第1张图片

(来自y总)

注:1.朴素Dijkstra适用于稠密图,堆优化Dijkstra适用于稀疏图。

(对于稀疏图和稠密图的界定:m

2.对于单源最短路存在负权边的图,若题目有求边数 <= k的条件,则只能用BF算法不能用Spfa

3.单源和多源指求一个点或多个点到终点的距离

Dijkstra(朴素)

最短路问题模版总结_第2张图片

活动 - AcWing

思路:

1.先初始化dist数组和g数组置为无穷大,且将dist[1]=0(dist[x] 表示x到起点的距离)

2.迭代n次,每次找到集合st外的距离起点最近的点t

3.再用点t来更新其他的点的距离

时间复杂度:O(n2)

代码如下:

#include

using namespace std;

const int N = 510;

int n,m;

int g[N][N],dist[N];

bool st[N];

int Dijkstra()
{
	memset(dist,0x3f,sizeof(dist));

	dist[1]=0;

	for(int i=1;i<=n;i++)
	{
		int t = -1;
		for(int j=1;j<=n;j++)
		{
			if(!st[j]&&(t==-1||dist[t]>dist[j]))
			{
				t=j;
			}
	}
        //if(t==n) break;
		//将t加入集合
		st[t]=true;
		//用t来更新其他点的距离
		for(int k=1;k<=n;k++)
		{
			dist[k]=min(dist[k],dist[t]+g[t][k]);
		}
	}

	if(dist[n]==0x3f3f3f3f)return -1;//1-n不连通
	return dist[n];
}
int main()
{
	memset(g,0x3f,sizeof(g));

	cin>>n>>m;

	while(m--)
	{
		int a,b,c;
		cin>>a>>b>>c;
		
		g[a][b]=min(g[a][b],c);
	}

	cout<

Dijkstra(堆优化)

最短路问题模版总结_第3张图片

活动 - AcWing

代码如下:

#include
#include
#include
#include

using namespace std;
const int N=1e6+10;
typedef pair PII;
int n,m;//输入n,m 节点数量和边数
int h[N],e[N],ne[N],idx,w[N];//邻接表
int d[N];//存放距离数组
bool st[N];//存放相应的状态


void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
    //首先明确我们的邻接表放的是什么东西
    //e[idx]里面放的是b,b是我们的另一个点,而idx更像是地址,
    //我们则需要通过地址来获取我们需要的b数据,和c数组权重
}


int dijkstra(){

    memset(d,0x3f,sizeof d);//朴素算法的要求初始化
    d[1]=0;

    priority_queue,greater> q;  //优先队列--小根堆

    q.push({0,1}); //存放编号和编号对应距离的集合

    while(q.size()){
        auto t=q.top();//取出第一个元素
        q.pop();//弹出第一个元素

        int ver=t.second,dis=t.first;//取出编号和距离

        if(st[ver]) continue;//这个点确定了,打上烙印了,不走回头路
        st[ver]=true;//打上烙印

        //h[ver]代表我们要遍历的那一个链表里面的第一个结点的地址,记住!!!
        //must remeber it,please!h[ver]是元素地址不是元素,本人老犯的一个病
        for(int i=h[ver];i!=-1;i=ne[i]){
            int j=e[i];//通过地址取出我们的b点即我们要遍历的那个点

            //给自己的忠告:d[ver]和dis的区别:dis是我们这个点ver到1点的距
            //d[ver]是ver这个点到1点的距离,所以没有区别,所以用哪个都行~ha

            //注意:我们的w是通过idx地址来取的因为w[i]不是w[j],
            //再次通过我们的输入顺序a,b,c,存放的时候是e[idx]=b,w[idx]=c,因此
            //b------------c可以说是一一对应关系。翻译:b的前面一条路的权重
            //h[ver]取的是ver后面的一个元素的首地址,所用取前面的路的权重
            //便是取w[i]

            if(d[j]>(d[ver]+w[i]))
            {
                d[j]=d[ver]+w[i];//不满足最小距离更新
                q.push({d[j],j});//遍历到下一个点
            }
        }


    }

    //判断是否到达最后一点,4个字节所以4个3f
    if(d[n]==0x3f3f3f3f) return -1;
    return d[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<

Bellman-Ford

最短路问题模版总结_第4张图片

853. 有边数限制的最短路 - AcWing题库

思路:

1.存储方法:用结构体存储a到b权重为w的边

2.迭代n次,每次用dist数组更新每个点的最短路径(松弛操作)其中每次备份dist数组保证用上次更新过的点来更新下一次,这也是spfa算法队列优化的原理。

注:若有负权回路则不存在最短路径。

对于串联效应的解释:(也就是为什么需要备份数组)

因为有边数限制,每次迭代是往前迈一步进行更新最短距离,每次迈一步需要基于上一次的状态,所以需要备份数组,否则用更新后的状态再更新就会发生错误。

最短路问题模版总结_第5张图片

代码如下:

#include

using namespace std;

const int N = 510,M = 10010,INF = 0x3f3f3f3f;

int dist[N],backup[N];

struct Edge{
	int a,b,w;
}edges[M];

int n,m,k;

int bellman_ford()
{
	memset(dist,0x3f,sizeof(dist));
	dist[1]=0;

	for(int i=0;iINF/2)return INF;//由于值为0x3f3f3f3f的点可以经过负权边更新为小一点的值,所以可以除以2判断
	return dist[n];
}
int main()
{
	cin>>n>>m>>k;

	for(int i=0;i>a>>b>>w;
		edges[i]={a,b,w};
	}

	int t = bellman_ford();

	if(t==INF)cout<<"impossible"<

SPFA

活动 - AcWing

最短路问题模版总结_第6张图片

思路:

1.spfa是用队列来优化bf算法.

2.队列里面存在的就是待更新的点,也就是变小过的点才能够使后面的点更小。

3.spfa中的st数组表示这个点是否在队列中,防止重复。

4.类似与多源点的BFS。

为什么和BF算法的判断不一样:

代码如下: 

#include 
#include 
#include 
using namespace std;

const int N = 100010;
int h[N], e[N], w[N], ne[N], idx;//邻接表,存储图
int st[N];//标记顶点是不是在队列中
int dist[N];//保存最短路径的值
int q[N], hh, tt = -1;//队列

void add(int a, int b, int c){//图中添加边和边的端点
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

void spfa(){
    q[++tt] = 1;//从1号顶点开始松弛,1号顶点入队
    dist[1] = 0;//1号到1号的距离为 0
    st[1] = 1;//1号顶点在队列中
    while(tt >= hh){//不断进行松弛
        int a = q[hh++];//取对头记作a,进行松弛
        st[a] = 0;//取完队头后,a不在队列中了
        for(int i = h[a]; i != -1; i = ne[i])//遍历所有和a相连的点
        {
            int b = e[i], c = w[i];//获得和a相连的点和边
            if(dist[b] > dist[a] + c){//如果可以距离变得更短,则更新距离

                dist[b] = dist[a] + c;//更新距离

                if(!st[b]){//如果没在队列中
                    q[++tt] = b;//入队
                    st[b] = 1;//打标记
                }
            }
        }
    }
}
int main(){
    memset(h, -1, sizeof h);//初始化邻接表
    memset(dist, 0x3f, sizeof dist);//初始化距离
    int n, m;//保存点的数量和边的数量
    cin >> n >> m;
    for(int i = 0; i < m; i++){//读入每条边和边的端点
        int a, b, w;
        cin >> a >> b >> w;
        add(a, b, w);//加入到邻接表
    }
    spfa();
    if(dist[n] == 0x3f3f3f3f )//如果到n点的距离是无穷,则不能到达 
        cout << "impossible";
    else cout << dist[n];//否则能到达,输出距离
    return 0;
}

SPFA判负环

最短路问题模版总结_第7张图片

思路:

代码如下:

#include 
#include 
#include 
using namespace std;

const int N = 100010;
int h[N], e[N], w[N], ne[N], idx;//邻接表,存储图
int st[N];//标记顶点是不是在队列中
int dist[N];//保存最短路径的值
int q[N], hh, tt = -1;//队列
int cnt[N];
int n, m;//保存点的数量和边的数量
void add(int a, int b, int c){//图中添加边和边的端点
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

bool spfa(){
    //1有可能不是源点
    for(int i=1;i<=n;i++)
    {
        st[i]=true;
        q[++tt]=i;
    }
    while(tt >= hh){//不断进行松弛
        int a = q[hh++];//取对头记作a,进行松弛
        st[a] = 0;//取完队头后,a不在队列中了
        for(int i = h[a]; i != -1; i = ne[i])//遍历所有和a相连的点
        {
            int b = e[i], c = w[i];//获得和a相连的点和边
            if(dist[b] > dist[a] + c){//如果可以距离变得更短,则更新距离

                dist[b] = dist[a] + c;//更新距离
                cnt[b] = cnt[a] + 1;
                if(cnt[b]>=n)return true;
                if(!st[b]){//如果没在队列中
                    q[++tt] = b;//入队
                    st[b] = 1;//打标记
                }
            }
        }
    }
    return false;
}
int main(){
    memset(h, -1, sizeof h);//初始化邻接表
    cin >> n >> m;
    for(int i = 0; i < m; i++){//读入每条边和边的端点
        int a, b, w;
        cin >> a >> b >> w;
        add(a, b, w);//加入到邻接表
    }
    if(spfa())cout<<"Yes"<

Floyd

活动 - AcWing

最短路问题模版总结_第8张图片思路:

1.邻接矩阵存储图

2.三重循环暴力解决最短路

基于dp

最短路问题模版总结_第9张图片

代码如下: 

#include 
using namespace std;

const int N = 210, M = 2e+10, INF = 1e9;

int n, m, k, x, y, z;
int d[N][N];

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() {
    cin >> n >> m >> k;
    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] = INF;
    while(m--) {
        cin >> x >> y >> z;
        d[x][y] = min(d[x][y], z);
        //注意保存最小的边
    }
    floyd();
    while(k--) {
        cin >> x >> y;
        if(d[x][y] > INF/2) puts("impossible");
        else cout << d[x][y] << endl;
    }
    return 0;
}

复习小结~~

最短路问题模版总结_第10张图片

最短路问题模版总结_第11张图片

你可能感兴趣的:(最短路问题,Acwing,算法,c++,图论,数据结构,宽度优先,动态规划,深度优先)