Minyou03

导航

最短路问题

思维导图
图论:最短路问题
一、单源最短路问题--所有边均为正权边--dijkstra算法
dijkstra算法基于贪心,思路本质上就是从节点1开始,一遍一遍找距离节点1最近的点,找到最近的点之后标记并用该点更新其所相邻的点与1的距离,
之后再从未标记的点中找到距离最短的点(实际上只是找标记点的临点)//堆优化也是优化的这个地方

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 10010,M = 20020 INF = 0x3f3f3f;
int m, n;
int h[N], e[M], w[M], ne[M], idx;

int g[N][N];
int dist[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

// 朴素版dijkstra算法(稠密图)--邻接矩阵:O(n^2+m)
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for (int i = 0; i < n - 1; i++)//集合中已经
    {
        int t = -1;
        for (int j = 1; j <= n; j++)
        {
            if (!st[j] && (t == -1 || dist[j] < dist[t]))
            {
                t = j;
            }
        }
        st[t] = true;
        for (int j = 1; j <= n; j++)
        {
            dist[j] = min(dist[j], dist[t] + g[t][j]);
        }
    }
    if (dist[n] == INF)
        return -1;
    else
        return dist[n];
}
// 堆优化版dijkstra算法(稀疏图)--邻接表O(mlogn)
int dijkstra()
{
    memset(dist, 0x3f3f3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap; // 小根堆就是这样写,往里push的时候只看第一个就可以了,如果写成priority_queue<PII>heap;
                                                         // 就是默认大根堆了
    heap.push({0, 1});
    while (heap.size())
    {
        auto t = heap.top();
        int ver = t.second, distance = t.first;
        if (st[ver])
            continue;
        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > w[i] + distance)
            {
                dist[j] = w[i] + distance;
                heap.push({dist[i], j});
            }
        }
        st[ver] = true;
    }
    if (dist[n] == 0x3f3f3f)
        return -1;
    return dist[n];
}
int main()
{
    cin >> m >> n;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            if (i == j)
                g[i][j] == 0;
            else
                g[i][j] = INF;
        }
    }
//邻接矩阵
    while (m--)
    {
        int a, b, w;
        cin >> a >> b >> w;
        g[a][b] = min(g[a][b], w); // 因为a与b之间可能有多条边,最短路就取得最小值就好了
    }
//邻接表
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);                //邻接表存储有重边也无所谓了
    }

    int t = dijkstra();
    cout << t << endl;
    return 0;
}

二、单源最短路--存在负权边--Bellman-Ford算法和SPFA算法
Bellman_ford算法思路:对所有点每次对所有边全部遍历一遍,每条边都松弛n次,就会得到最短路。
spfa算法思路:是Bellman_ford算法利用队列优化,只有那些在前一遍松弛中改变了距离估计值的点,才可能引起他们的邻接点的距离估计值的改变

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 10010,M = 20010, INF = 0x3f3f3f;

int m, n, k;
int h[N], e[M], w[M], ne[M], idx;
bool st[N];
void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

int dist[N], bakcup[N];
struct
{
    int a, b;
    int w;
} edge[N];
int Bellman_ford() // 有边数限制
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for (int i = 0; i < k; i++)
    {
        memcpy(dist, bakcup, sizeof dist);
        for (int j = 1; j <= m; j++)
        {
            int a = edge[j].a, b = edge[j].b, w = edge[j].w;
            if (dist[b] > bakcup[a] + w)
                dist[b] = bakcup[a] + w;
        }
    }
    if (dist[n] > INF / 2)
        return -1;
    return dist[n];
}
// 无边数限制
int Bellman_ford()
{ 
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            int a = edge[j].a, b = edge[j].b, w = edge[j].w;
            if (dist[b] > dist[a] + w)
                dist[b] = dist[a] + w;
        }
    }
    if (dist[n] > INF / 2)
        return -1;
    return dist[n];
}

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]){
                    st[j]=true;
                    q.push(j);
                }
            }
        }
    }
    if(dist[n]==INF) return -1;
    return dist[n];
}
//spfa判断是否存在负环
int cnt[N];
bool spfa_c(){
    queue<int>q;//因为这是有向图,所以仅从1这个点并不能完全判断图中是否存负环
    for(int i=1;i<=n;i++){
        q.push(i);
        st[i]=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];
                cnt[j]=cnt[t]+1;//这里就和dfs中记录字数个数一样,cnt用来记录1到j点的个数(不包含自己),如果点数超过n
                                //由抽屉定理就可以判断有负环了
                if(cnt[j]>=n) return true;
                if(!st[j]){
                    st[j]=true;
                    q.push(j);
                }
            }
        }
    }
    return false;
}

int main()
{
    cin >> m >> n;
    for (int i = 1; i <= m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        edge[i].a = a, edge[i].b = b, edge[i].w = c;
    }
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }
    
    return 0;
}

三、多源最短路--floyd算法
基于动态规划实现

#include<bits/stdc++.h>
using namespace std;
const int N = 100010,INF=0x3f3f3f;
int n,m,Q;
int d[N][N];

//用于计算d[a][b],表示a到b的距离,将所有距离全部算好
int floyd(){
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
            }
        }
    }
}

int main(){
    cin>>m>>n>>Q;
    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--){
        int a,b,c;
        cin>>a>>b>>c;
        d[a][b]=min(d[a][b],c);
    }
    floyd();
    while(Q--){
        int x,y;//求x到y的最短距离,源点不唯一,所以用floyd算法
        cin>>x>>y;
        if(d[x][y]>INF/2) cout<<"impossible"<<endl;//因为可能存在负权边,所以当x和y之间不存在通路时,d[x][y]不一定是正无穷
        else cout<<d[x][y]<<endl;
    }
    return 0;
}

posted on 2024-10-23 14:18  Minyou0713  阅读(16)  评论(0编辑  收藏  举报