图论——最短路

目录

一、Dijkstra算法

 1、朴素Dijkstra算法 

2、堆优化Dijkstra算法

二、Bellman_ford算法

三、spfa算法

1、 spfa求最短路

2.spfa判断负环

 四、Floyd算法


图论——最短路_第1张图片

图论——最短路_第2张图片

图论——最短路_第3张图片

一、Dijkstra算法

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。

输入格式

第一行包含整数 n 和 m。

接下来 mm 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 −1。

数据范围

1≤n≤500,
1≤m≤10 5,
图中涉及边长均不超过10000。

输入样例:

3 3
1 2 2
2 3 1
1 3 4

输出样例:

3

 1、朴素Dijkstra算法 

图论——最短路_第4张图片

Dijkstra 的整体思路比较清晰
即进行n(n为n的个数)次迭代去确定每个点到起点的最小值 最后输出的终点的即为我们要找的最短路的距离

所以按照这个思路除了存储图外我们还需要存储两个量

dist[n] //用于存储每个点到起点的最短距离
st[n]   //用于在更新最短距离时 判断当前的点的最短距离是否确定 是否需要更新

每次迭代的过程中我们都先找到当前未确定的最短距离的点中距离最短的点
(至于为什么是这样那么这就涉及到Dijkstra算法的具体数学证明了 有兴趣的同学可以百度一下)

int t=-1;       //将t设置为-1 因为Dijkstra算法适用于不存在负权边的图
for(int j=1;j<=n;j++)
{
    if(!st[j]&&(t==-1||dist[t]>dist[j])    //该步骤即寻找还未确定最短路的点中路径最短的点
        t=j;
}


通过上述操作当前我们的t代表就是剩余未确定最短路的点中 路径最短的点
而与此同时该点的最短路径也已经确定我们将该点标记

st[t]=true;

然后用这个去更新其余未确定点的最短距离

for(int j=1;j<=n;j++)
    dist[j]=min(dist[j],dist[t]+g[t][j]);
//这里可能有同学要问j如果从1开始的话 会不会影响之前已经确定的点的最小距离
//但其实是不会 因为按照我们的Dijkstra算法的操作顺序 先确定最短距离的点的距离已经比后确定的要小 所以不会影响
//当然你也可以在循环判断条件里加上if(!st[i])
//这里j从1开始只是为了代码的简洁

进行n次迭代后最后就可以确定每个点的最短距离
然后再根据题意输出相应的 要求的最短距离

#include
using namespace std;
const int N=510;
int dist[N];     //用于记录每一个点距离第一个点的距离
int g[N][N];     //稠密图一般使用邻接矩阵
bool st[N];      //用于记录该点的最短距离是否已经确定
int n,m;

int dijkstra()
{
    //所有节点距离起点的距离初始化为无穷大
    memset(dist,0x3f,sizeof dist);  
    //起点距离自己的距离为零
    dist[1] = 0;
    
    //迭代n次,每次可以确定一个点到起点的最短路
    for(int i=0;i>n>>m;
    
    memset(g,0x3f,sizeof g);    //初始化图 因为是求最短路径
                                //所以每个点初始为无限大
    
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b] = min(g[a][b], c); //如果有重边,请保留权值最小的一条边
    }
    cout << dijkstra() << endl;
}

2、堆优化Dijkstra算法

图论——最短路_第5张图片

图论——最短路_第6张图片

注意:若要求任意点 i 到任意个点 j 的最短距离,只需修改dijkstra方法中的起源位置dist[i] = 0,以及返回为dist[j] 

#include
#include
#include
#include

using namespace std;
typedef pair PII; //<离起点的距离, 节点编号>

const int N = 150010;
int h[N], e[N], ne[N], idx, w[N];
int dist[N];
bool st[N];
int n, m;

//在a节点之后插入一个b节点,权重为c
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);
//    1号节点距离为0
    dist[1] = 0;
//    建立一个小根堆
    priority_queue, greater> heap;
//    1号节点插入堆
    heap.push({0, 1});
    while (heap.size()) {
//        取出堆顶顶点
        auto t = heap.top();
//        并删除
        heap.pop();
//        取出节点编号和节点距离
        int ver = t.second, distance = t.first;
//        如果节点被访问过,则跳过
        if (st[ver])continue;
        st[ver] = true;
        for (int i = h[ver]; i != -1; i = ne[i]) {
//            取出节点编号
            int j = e[i];
//            dist[j] 大于从t过来的距离
            if (dist[j] > distance + w[i]) {

                dist[j] = distance + w[i];
                heap.push({dist[j], j});
            }
        }
    }
    if (dist[n] == 0x3f3f3f3f)return -1;
    return dist[n];

}

int main() {
    memset(h, -1, sizeof h);
    cin >> n >> m;
    while (m--) {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    cout << dijkstra() << endl;
    return 0;
}

二、Bellman_ford算法

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你求出从 1 号点到 n 号点的最多经过 kk 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible

注意:图中可能 存在负权回路 。

输入格式

第一行包含三个整数 n,m,k。

接下来 mm 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

点的编号为 1∼n。

输出格式

输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。

如果不存在满足条件的路径,则输出 impossible

数据范围

1≤n,k≤500,
1≤m≤10000,
1≤x,y≤n,
任意边长的绝对值不超过 10000。

输入样例:

3 3 1
1 2 1
2 3 1
1 3 3

输出样例:

3

图论——最短路_第7张图片

dijkstra 为什么不能解决解决负权边最短路问题

图论——最短路_第8张图片


加入每条边去松弛每个点到起点的距离

dist[b] = min(dist[b], backup[a] + w);


为什么需要back[a]数组
为了避免如下的串联情况, 在边数限制为一条的情况下,节点3的距离应该是3,但是由于串联情况,利用本轮更新的节点2更新了节点3的距离,所以现在节点3的距离是2。

图论——最短路_第9张图片

正确做法是用上轮节点2更新的距离--无穷大,来更新节点3, 再取最小值,所以节点3离起点的距离是3。

图论——最短路_第10张图片

for (int i = 0; i < k; i ++ )
{
    memcpy(backup, dist, sizeof dist);
    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], backup[a] + w);
    }
}


为什么是dist[n]>0x3f3f3f3f/2, 而不是dist[n]>0x3f3f3f3f
5号节点距离起点的距离是无穷大,利用5号节点更新n号节点距离起点的距离,将得到109−2109−2, 虽然小于109109, 但并不存在最短路,(在边数限制在k条的条件下)。

if(dist[n]>0x3f3f3f3f/2) return -1;
else return dist[n];
#include
using namespace std;
const int N=510,M=10010;

int dist[N];
int backup[N];  //备份数组防止串联
int n,m,k;      //k代表最短路径最多包涵k条边

struct Edge
{
    int a,b,w;
}edge[M];      //把每个边保存下来即可

int bellman_ford()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    for(int i=0;i>n>>m>>k;
    for(int i=0;i>edge[i].a>>edge[i].b>>edge[i].w;
    int t=bellman_ford();
    if(dist[n] > 0x3f3f3f3f / 2)
    puts("impossible");
    else 
    cout<

三、spfa算法


1、 spfa求最短路

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible

数据保证不存在负权回路。

输入格式

第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 impossible

数据范围

1≤n,m≤10 5,
图中涉及边长绝对值均不超过 10000。

输入样例:

3 3
1 2 5
2 3 -3
1 3 4

输出样例:

2

图论——最短路_第11张图片

图论——最短路_第12张图片

图论——最短路_第13张图片

图论——最短路_第14张图片

图论——最短路_第15张图片

图论——最短路_第16张图片

#include
using namespace std;
const int N=1e5+10;
int n,m;
int h[N],w[N],e[N],ne[N],idx;//邻接表,存储图
int dist[N];
int st[N];


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

int spfa()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    queue q;
    q.push(1);
    st[1]=true;
    
    while(q.size())
    {
        int t=q.front();
        q.pop();
        st[t]=false;//从队列中取出来之后该节点st被标记为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])//当前已经加入队列的结点,无需再次加入队列,即便发生了更新也只用更新数值即可,重复添加降低效率
                {
                    q.push(j);
                    st[j]=true;
                    
                }
            }
        }
    }

    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);
    }
    int t=spfa();
    if(t==0x3f3f3f3f)
    puts("impossible");
    else
    cout<

2.spfa判断负环

图论——最短路_第17张图片

图论——最短路_第18张图片

#include
using namespace std;
const int N=1e5+10;
int n,m;
int h[N],w[N],e[N],ne[N],idx;//邻接表,存储图
int dist[N],cnt[N];
int st[N];


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

int spfa()
{
    queue q;
    
    for(int i=1;i<=n;i++)
    {
        st[i]=true;
        q.push(i);
    }
    
    while(q.size())
    {
        int t=q.front();
        q.pop();
        st[t]=false;//从队列中取出来之后该节点st被标记为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;
                if(cnt[j]>=n)return true;
                if(!st[j])//当前已经加入队列的结点,无需再次加入队列,即便发生了更新也只用更新数值即可,重复添加降低效率
                {
                    q.push(j);
                    st[j]=true;
                    
                }
            }
        }
    }

    return false;
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }
    if(spfa())
    puts("Yes");
    else
    puts("No");
    return 0;
}

 四、Floyd算法

多源汇最短路问题-具有多个源点
Floyd算法 O(n^3)-动态规划
给定一个n个点m条边的有向图,图中可能存在重边和自环,边权可能为负数。

再给定k个询问,每个询问包含两个整数x和y,表示查询从点x到点y的最短距离,如果路径不存在,则输出“impossible”。

数据保证图中不存在负权回路。

输入格式
第一行包含三个整数n,m,k

接下来m行,每行包含三个整数x,y,z,表示点x和点y之间存在一条有向边,边长为z。

接下来k行,每行包含两个整数x,y,表示询问点x到点y的最短距离。

输出格式
共k行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出“impossible”。

数据范围
1≤n≤2001≤n≤200
1≤k≤n21≤k≤n2
1≤m≤200001≤m≤20000
图中涉及边长绝对值均不超过10000。

输入样例:
3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3
输出样例:
impossible
1

#include
using namespace std;

const int N=210,INF=1e9;
int n,m,Q;
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>>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,w;
        cin>>a>>b>>w;
        d[a][b]=min(d[a][b],w);
        //注意保存最小的边
    }
    floyd();
    while(Q--)
    {
        int a,b;
        cin>>a>>b;
        if(d[a][b]>INF/2)//由于有负权边存在所以约大过INF/2也很合理
        puts("impossible");
        else
        cout<

你可能感兴趣的:(图论,图论)