最短路问题

文章介绍了几种单源最短路径算法,包括Dijkstra算法的标准邻接矩阵版本和堆优化后的稀疏图版本,以及处理负权边的Bellman-Ford算法和SPFA算法。此外,还提到了多源最短路的Floyd算法。
摘要由CSDN通过智能技术生成

总结:

单源最短路 

仅有正权边

dujkstra算法:【贪心】

【模板】单源最短路径(标准版) - 洛谷

代码实现:

一、邻接矩阵版本:用于边数较多的稠密图

代码实现:

int n, m,s; //n个点,m个边,源点为s
int g[N][N]; //临界矩阵记录距离
int vis[N];//用于判断当前点是否属于s中,已经算过最短距离的点
int dis[N];//每个点到源点的距离
int p[N];//记录到第i个点的最短路径
void dijsktra(int u)
{
	for (int i = 1; i <= n; i++)
	{
		vis[i] = false;
		dis[i] = inf;
		p[i] = -1;
	}
	//初始化,有用的值的初始化

	dis[u] = 0;
	//源点初始化距离为0
	//有n个点,每一次选择当前最短的最优解,所以要遍历n-1次

 	for (int i = 2; i <= n; i++)
	{
		//查找属于s-v的最短路径的点
		int t = -1, Min = inf;
		for (int j = 1; j <= n; j++)
		{
			if (!vis[j] && (t == -1 || Min > dis[j]))
			{
				t = j;
				Min = dis[t];
			}
		}
		vis[t] = true;
		//标记点属于s里面了
		//遍历所有点,松弛操作,更新
		for(int j=1;j<=n;j++)
		{
			if (!vis[j] && dis[j] > Min + g[t][j])
			{
				dis[j] = Min + g[t][j];
				p[j] = t;
			}
		}	
	}
	for (int i = n; i <= n; i++)
	{
		//若该点没有连接
		if (dis[i] >= inf)
			cout <<  - 1 << " ";
		else
			cout << dis[i] << " ";
	}
	//回溯输出路径
	int t = n;
	while (p[t] != -1)
	{
		cout << p[t] << " ";
		t = p[t];
	}
	

}


void solve()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			g[i][j] = inf;
	while (m--)
	{
		int a, b, c;
		cin >> a >> b >> c;
		g[a][b] = min(g[a][b], c);
	}
	dijsktra(1);
	
}

二、堆优化【稀疏图,用最小优先队列使每次更新的小值存入数组中】

代码实现:

struct Edge
{
	int to, ne, w;
}edge[N * 2 + 5];//特别注意,边的数量是多少啊!!!
int h[N];
int idx = 0;
void init()
{
	memset(h, -1, sizeof h);
	idx = 0;
}
void add(int u, int v, int w)
{
	edge[idx].to = v; edge[idx].w = w; edge[idx].ne = h[u]; h[u] = idx++;
}
int n, m, s; //n个点,m个边,源点为s
int vis[N];//用于判断当前点是否属于s中,已经算过最短距离的点
int dis[N];//每个点到源点的距离
int p[N];//记录到第i个点的最短路径
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
void dijsktra(int u)
{
	q.empty();
	for (int i = 1; i <= n; i++)
	{
		vis[i] = false;
		dis[i] = inf;
		p[i] = -1;
	}
	dis[u] = 0;
	q.push({ 0, u });
	while (q.size())
	{
		pair<int, int> now = q.top();
		q.pop();
		if (vis[now.second] == true)
			continue;
		int t = now.second, Min = now.first;
		vis[t] = true;
		//t为当前最短路径的点,Min为最短路径的长度
		//遍历所有这个点连接到的点,并且把点的距离进行疏松
		for (int i = h[t]; i != -1; i = edge[i].ne)
		{
			//i枚举的是连接点的边的编号
			int v = edge[i].to, w = edge[i].w;
			//v记录的是连接的点,对这个点进行疏松,如果值变小了,就可以,加入q里面
			if (dis[v] > Min + w)
			{
				dis[v] = Min + w;
				q.push({ dis[v],v });
				p[v] = t;
			}
		}
	}
	for (int i = 1; i <= n; i++)
	{

		if (dis[i] >= inf)
			cout <<(1<<31-1) << " ";
		else
			cout << dis[i] << " ";
	}
}

void solve()
{
	init();
	cin >> n >> m>>s;
	while (m--)
	{
		int a, b, v;
		cin >> a >> b >> v;
		add(a, b, v);
	}
	dijsktra(s);
}

收穫++:

1.堆優化版本:小耿堆存儲<路徑長度,對應的終點>,每次有更新路徑較小的值且從來沒有入過堆,較小路徑和對應的點存入堆中,對中的點最多n個。堆中【st[i]=true】存的是s,堆外最短路徑可以改變的,比較的是[st[i]=false】存的是s-v。

2.dijsktra函數要先預處理,路徑長度inf化,st/vis false化,路徑p-1化

存在负权边

bellman-ford算法

活动 - AcWing

【动态规划,当前点的状态由上一点转移】

代码实现:

struct EEdeg
{
	int a, b, w;
}edges[M];
int n, m, k;
int dis[N];
int back[N];
void bellman_ford(int k)
{
	for (int i = 1; i <= n; i++)
		dis[i] = inf;
	//初始化
	dis[1] = 0;
	for (int t = 0; t < k; t++)
	{//限制有k条边
		memcpy(back, dis, sizeof(dis));
		//备份操作,eg.滚动数组
		for (int i = 1; i <= m; i++)
		{//遍历所有的边,球min值
			int a = edges[i].a, b = edges[i].b, w = edges[i].w;
			dis[b] = min(dis[b], back[a] + w);
		}
	}
	//
	if (dis[n] >=inf / 2)
		cout <<"impossible" << endl;
	else
		cout << dis[n] << endl;
}
void solve()
{
	cin >> n >> m >> k;
	for (int i = 1; i <= m; i++)
	{
		cin >> edges[i].a >> edges[i].b >> edges[i].w;
	}
	bellman_ford(k);

}

spfa算法

【模板】单源最短路径(标准版) - 洛谷

【一般可以用dijsktra算法都可以用spfa算法,如果被卡了,再回去;负权值一定可以用spfa算法】

2.是由bellman_ford算法的堆优化版本,值只有改变了才会对后面相连的线有影响

代码实现:

struct Edge
{
	int to, ne, w;
}edge[M];
int h[1000001];
int vis[1000001];
int idx = 0;
void init()
{
	memset(h, -1, sizeof h);
	idx = 0;
}
void add(int u, int v, int w)
{
	edge[idx].to = v; edge[idx].w = w; edge[idx].ne = h[u]; h[u] = idx++;
}
int n, m, k,s;
int dis[N];
int back[N];
int times[N];
void sofa(int s)
{
    queue<int>q;
    for (int i = 1; i <= n; i++)
    {
        dis[i] = inf;
        vis[i] = false;
        times[i]=0;
    }
    dis[s] = 0;
    q.push(s);
    vis[s] = true;
    //当前的值改变,需要放入队列,对其后面的值进行改变
    while (q.size())
    {
        int u = q.front();
        q.pop();
        
        vis[u] = false;
        //表示当前的点已经不在队列了,也就是如果值改变还得入堆
        
        for (int i = h[u]; i != -1; i = edge[i].ne)
        {
            //遍历以当前的点为起点的终边,对于上一个点路径的改变是否会使路径变短
            int v = edge[i].to,w=edge[i].w;
            if (dis[v] > (dis[u] + w))
            {
                dis[v]=dis[u] + w;
                if (vis[v]==false)
                {
                    //若使路径变短了,且不在存储路径有改变的点堆里面,就存入
                    q.push(v);
                    vis[v] = true;
                    times[v]++;
                    if(times[v]>=n+1)
                    {
                        cout<<"YES"<<endl;//存在负环
                        return;
                    }
                    
                }
            }
        }
    }
    for(int i=1;i<=n;i++)
        cout<<dis[i]<<" ";//不存在负环输出最短路径。
}
void solve()
{
	init();
	cin >> n >> m>>s ;
	for (int i = 1; i <= m; i++)
	{
		int a, b, c;
		cin >> a >> b >> c;
		add(a, b, c);
	}
	sofa(s);

}

收获++:

1.核心,只有当前的值改变了,路径变短才会对它的子树枝产生影响,有可能使路径变短

2.队列记录路径改变的点,也就是后面的子枝条要改变的点,vis=false=非天使组织,不影响】记录当点对后面的已经没有影响,vis=true=天使组织,大发善心让后面的路径变短】说明正在队列中等待影响后面的人,所以初始false非天使都没有影响,存入队列后true天使,天使开始大法慈心VS dijsktra的堆记录的是最短路径,vis用于判断路径已经确定,不再受后面的队列的影响)

多源最短路

Floyd算法【动态规划】

#include <iostream>

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");

        //由于有负权边存在所以约大过INF/2也很合理

        else cout << d[x][y] << endl;

    }



    return 0;

}

 收获++:

对于多源汇最短路来说,很简单和bellman-ford算法有点异曲同工之处,每个点每条边每次都进行一次疏松!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值