图论 _ 基本最短路算法

约定:
n是指点的数量,m是指边的数量

目录:
image

Dijkstra算法

Dijkstra算法只能用于所有边权均为非负数值的图
Dijkstra算法有两种实现:
一种是朴素实现,复杂度是 $O(n^2) $
一种是堆优化版,复杂度是 \(O(m \log n)\)

堆优化版+邻接表存图有以下优点:

  1. 不需要对重边做处理。
  2. 不需要对自环做处理

过程

补: 优先队列:带权值的队列

把优先队列的权值设为:离结点1的距离
image

如果某些结点已经在优先队列,不用删除,只需要再放入一个带新权值的该结点即可。
image
如上,已经走过的点不需要再入队,记得更新parent数组
image
优先队列中出队元素,如果已经走过,直接删除即可(不做任何操作)
image

代码


#include<bits/stdc++.h>
using namespace std;
#define ll long long

const ll N=1e5 + 5;

typedef pair<ll, ll> PII;
//因为优先队列用结构体创建需要设置的东西太多,所以使用pair
//而且必须是第一个结点代表:路径长度,第二个结点代表结点数
//原因: 优先队列中greater<PII>排序默认对第二个结点排序
//如果反着放需要重写operator函数
ll n,m,x,y;
ll visited[N];//是否访问过某结点
long long  dist[N];//某结点距离


struct Node
{
    ll value;
    ll len;
};

vector<Node> g[N];//图

//堆优化版dijksyta
ll dijkstra()
{
     fill(dist, dist + N, 1e18);//距离默认无穷大

    priority_queue<PII, vector<PII>, greater<PII> > q;

    q.push({0, x});//传入首结点
    dist[x] = 0;//头结点距离为0

    while (q.size())
    {

        PII t = q.top();
        q.pop();
        ll distance = t.first;
        ll  ver = t.second;

        if (visited[ver]) continue;
        visited[ver] = 1;

            for(auto i:g[ver]) // 遍历ver的邻接点
        {
              ll v=i.value;
              ll k=i.len;
            ll distan = distance+k;


            if (dist[v] > distan)
            {
                dist[v] =distan;
                q.push( {dist[v],v} );
            }
        }
    }
    return dist[y];
}


int main()
{
       memset(visited,0,sizeof visited);
    cin>>n>>m>>x>>y;
    while(m--)
    {
        ll a,b;
        ll k;
        cin>>a>>b>>k;
        g[a].push_back({b,k});
        g[b].push_back({a,k});
    }

    ll ans=dijkstra();

  if(ans==1e18)ans=-1;
    cout<<ans<<endl;
    return 0;
}

模板

重写operartor示意

struct Node{
    int vec;
    int dis;
    bool operator < (const Node &t) const{
        return dis<t.dis;
    }
};

dij模板

int n, m;
vector<pii> g[N];
int vis[N];
int dist[N];
void dij(int be, int en)
{
    memset(dist, 0x3f, sizeof dist);
    priority_queue<pii, vector<pii>, greater<pii>> q;
    q.push({0, be});
    dist[be] = 0;
    while (q.size())
    {
        pii k = q.top();
        q.pop();
        if (vis[k.second])
            continue;
        vis[k.second] = 1;
        for (pii t : g[k.second])
        {
            int cnt = dist[k.second] + t.first;
            if (dist[t.second] > cnt)
            {
                dist[t.second] = cnt;
                q.push({cnt, t.second});
            }
        }
    }
    if (dist[en] <= 0x3f3f3f3f)
        cout << dist[en] << endl;
    else
        cout << -1 << endl;
}

输出路径模板

#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n, m, st, en, val[N], cnt[N], pre[N], dist[N], tot[N];
typedef pair<int, int> pii;
vector<pii> vec[N];
int vis[N];
void dj()
{
    me
    memset(dist, 0x3f, sizeof dist);
    priority_queue<pii, vector<pii>, greater<pii>> q;
    q.push({0, st});
     pre[st] = -1;//记得初始化
    dist[st] = 0;
    while (q.size())
    {
        pii k = q.top();
        q.pop();
        if (vis[k.second])
            continue;
        vis[k.second] = 1;
        for (pii t : vec[k.second])
        {
            int dis = t.first + dist[k.second];

            if (dist[t.second] > dis)
            {
                dist[t.second] = dis;
                pre[t.second] = k.second;//更新路径
                q.push({dis, t.second});
            }
            else if(dist[t.second]==dis) {//用于输出最短长度的经过结点最多的路径,如果不需要删除即可。
                cnt[t.second]++
                 dist[t.second] = dis;
                pre[t.second] = k.second;
                q.push({dis, t.second});
            }
        }
    }
    cout << dist[en] << endl;
}

//将路径逆置
stack<int> ans;
void print(int en)
{
    if (pre[en] == -1)
    {
      ans.push(en-1);
        return;
    }
    else
    {
       ans.push(en-1);
        print(pre[en]);
    }
}
signed main()
{
    cin >> n >> m >> st >> en;
    en++;
    st++;
    for (int i = 1; i <= n; i++)
        cin >> val[i];
    for (int i = 1; i <= m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        a++, b++;
        vec[a].push_back({c, b});
        vec[b].push_back({c, a});
    }
    dj();
    cout << cnt[en] << " " << tot[en] << endl;
    print(en);
	//输出路径
    while(ans.size()){
        if(ans.size()==1){
               cout<<ans.top()<<endl;
        }else  cout<<ans.top()<<" ";
        ans.pop();

    }
    return 0;
}

实战

D - Train( AtCoder - abc192_e)

#include<bits/stdc++.h>
using namespace std;
#define ll long long

const ll N=1e5 + 5;

typedef pair<ll, ll> PII;
// 定义一个二元组PII,第一维:距离,第二维:点
/*
 *第一维用.first引用;
 *第二维用.second引用
*/


ll n,m,x,y;
ll visited[N];
long long  dist[N];//距离


struct Node
{
    ll v;
     ll t,k;
    //城市v
    //花费时间t
    //发车时间k

};
vector<Node> g[N];


//堆优化版dijksyta
ll dijkstra()
{
     fill(dist, dist + N, 1e18);
    // 初始化距离为无穷

    priority_queue<PII, vector<PII>, greater<PII> > q;
    // 定义一个按照距离从小到大排序的优先队列,第一维:距离,第二维:点
    /*
    *第一个是数据类型int,char..
    *第二个是储存结构,直接填vector<前面数据类型>
    *第三个是按升序还是降序排列greater是升序
    */

    dist[x] = 0;
    // 源点x 距离为0
    q.push({0, x});
    // 把源点x信息放入优先队列

    while (q.size())
    {

        PII t = q.top();
        ll distance = t.first; // 最小距离
        ll  ver = t.second;    // 相对应的点
        q.pop();
        // 顶层元素(权值最小元素)出队




        if (visited[ver]) continue;
        // 保证每个点只出入队一次,
        visited[ver] = 1;
        // 标记,因为dijkstra的贪心策略保证每个点只需要进出队一次


            for(auto i:g[ver]) // 遍历ver的邻接点
        {
              ll v=i.v;
              ll t=i.t,   k=i.k;
            ll distan = (distance+k-1)/ k  * k +t;


            if (dist[v] > distan)
            {
                dist[v] =distan;

                q.push( {dist[v],v} );
                // 这里不需要判断visited,因为一旦更新发现更小必须放入队列
            }

        }
    }

    return dist[y];
}


int main()
{
       memset(visited,0,sizeof visited);
    cin>>n>>m>>x>>y;
    while(m--)
    {
        ll a,b;
        ll t,k;
        cin>>a>>b>>t>>k;
        g[a].push_back({b,t,k});
        g[b].push_back({a,t,k});
    }

    ll ans=dijkstra();

  if(ans==1e18)ans=-1;
    cout<<ans<<endl;
    return 0;
}



多源最短路 Floyed算法

思想

我们知道:从任意节点A到任意节点B的最短路径只有两种情况:

  • 1是直接从A到B
  • 2是从A经过若干个节点到B。

所以我们只需要将所有情况的中间路径遍历一边,取dist[i][j] =min( dist[i][j] ,dist[i][k] + dist[k][j] );即可。
对于如何遍历我们可以先假设只有一个中间节点:

假如现在只允许经过1号顶点,求任意两点之间的最短路程,应该如何求呢?只需判断dis[i][1]+dis[1][j]是否比dis[i][j]要小即可。(dis[i][j]表示的是从i号顶点到j号顶点之间的路程。dis[i][1]+dis[1][j]表示的是从i号顶点先到1号顶点,再从1号顶点到j号顶点的路程之和。) 其中i是1n循环,j也是1n循环 。
代码:

for(i=1;i<=n;i++)
{
    for(j=1;j<=n;j++)
    {
         dis[i][j]=min(dis[i][j],dis[i][1]+dis[1][j]);
    }
}

如果中间节点是1和2两个节点呢?
自然想到:

//经过1号顶点
for(i=1;i<=n;i++)
    for(j=1;j<=n;j++)
        dis[i][j]=min(dis[i][j],dis[i][1]+dis[1][j]);

//经过2号顶点
for(i=1;i<=n;i++)
    for(j=1;j<=n;j++)
        dis[i][j]=min(dis[i][j],dis[i][2]+dis[2][j]);

上面代码看着很美好,但是对吗?
image
对于上图A到B的最小路径是A->B->C->D ,上面代码可以得到正确结果吗?(设C为1号节点,D为2号节点)
当然可以,因为:第一次双重循环可以找到B到D的最短k路径B-C-D,而第2次双重循环可以找到B-(—C—)—D—A这条路
所以将代码一般化就是

for(int k=1; k<=n; k++)       //插入k点
    {
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=n; j++)
            {
                if(d[i][k]<INF&&d[k][j]<INF)     //消除加法溢出问题
                    d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
                      //更新两点距离
            }
        }
    }

这里再纠正一个错误:为什么不讲插入节点放入最里面的循环,而是放在最外层。
还是上面的例子
image
如果我们在最内层检查所有节点K,那么对于A->B,我们只能发现一条路径,就是A->B;显然是错误的。

代码

#include<stdio.h>
#include<iostream>
#include<limits.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int INF=INT_MAX/100;
int d[3000][3000],n,m;
void floyed()
{
    for(int k=1; k<=n; k++)       //插入k点
    {
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=n; j++)
            {
                if(d[i][k]<INF&&d[k][j]<INF)     //消除加法溢出问题
                    d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
                      //更新两点距离
            }
        }
    }
}

int main()
{
   cin>>n>>m;
    for(int i=0; i<=n; i++)
    {
        for(int j=0; j<=n; j++)
            if(i==j)
                d[i][j]=0;
            else
                d[i][j]=INF;
    }
    for(int i=0; i<m; i++)
    {
        int sta,stb,coc;
        cin>>sta>>stb>>coc;
        d[sta][stb]=coc;
         d[stb][sta]=coc;
    }
    floyed();
    int ans=INF;
    int ansid=0;
    for(int i=1;i<=n;i++){
         int cnt=0;
        for(int j=1;j<=n;j++){
            cnt=max(cnt,d[i][j]);
        }
        if(cnt!=INF){
        if(ans>cnt){
            ans=cnt;
            ansid=i;
        }
        }

    }
    if(ans==INF) cout<<"0"<<endl;
  else   cout<<ansid<<" "<<ans<<endl;
    return 0;
}
posted @ 2022-08-05 15:06  kingwzun  阅读(43)  评论(0编辑  收藏  举报