1. 程式人生 > >最短路和差分約束(三種演算法實現)( Til the Cows Come Home )

最短路和差分約束(三種演算法實現)( Til the Cows Come Home )

題目訓練連結(密碼hpuacm): https://vjudge.net/contest/246705

我會分別用 迪傑斯特拉  優先佇列和鏈式前向星優化過的迪傑斯特拉  SPFA演算法 三種方法講一下例題。

此外上述三種演算法是求單源最短路問題 這裡還會介紹一下多源最短路的演算法 floyd演算法。多源最短路可以求出任意兩點間的最短距離。

在存圖方式中會用到鄰接矩陣 鏈式前向星等存圖方式不知道的可以先了解一下

鄰接矩陣存圖   點這裡!!!

鏈式前向星      點這裡!!!

再介紹一位牛人的部落格    

點這裡!!!

提示: 二維矩陣或者一般用來點非常多邊也非常多的圖,就是稠密圖,vector存圖是點和邊都比較少的圖,存的是稀疏圖,

鏈式前向星存圖則限制條件比較小,鏈式前向星其實存的是邊。

上文的鏈式前向星的,博主沒有給出具體程式碼。我這裡給出

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000;

struct Edge{
    int to, val, next;
    // edge[i].to表示第i條邊的終點,
    //edge[i].next表示與第i條邊同起點的下一條邊的儲存位置,
    //edge[i].w為邊權值
};
struct Edge edge[MAXN];
int head[MAXN]; // 表示以i為起點的最後一條邊儲存的位置
int cnt = 0;
void add( int u, int v, int val )
{
    edge[cnt].to = v;
    edge[cnt].val = val;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}

int main()
{
    memset(head, -1, sizeof(head));
    cnt = 0;
    int n;
    int a, b, c;
    scanf("%d", &n);
    for( int i=0; i<n; i++ )
    {
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }
    int p;
    scanf("%d", &p);
    printf("u   v   val\n");
    for( int i=head[p]; i != -1; i = edge[i].next )
    {
        printf("%d  %d  %d\n", p, edge[i].to, edge[i].val);
    }


    return 0;
}

來道題理解一下,給出的程式碼可以作為模板使用

                                    Til the Cows Come Home

Bessie is out in the field and wants to get back to the barn to get as much sleep as possible before Farmer John wakes her for the morning milking. Bessie needs her beauty sleep, so she wants to get back as quickly as possible. 

Farmer John's field has N (2 <= N <= 1000) landmarks in it, uniquely numbered 1..N. Landmark 1 is the barn; the apple tree grove in which Bessie stands all day is landmark N. Cows travel in the field using T (1 <= T <= 2000) bidirectional cow-trails of various lengths between the landmarks. Bessie is not confident of her navigation ability, so she always stays on a trail from its start to its end once she starts it. 

Given the trails between the landmarks, determine the minimum distance Bessie must walk to get back to the barn. It is guaranteed that some such route exists.

Input

* Line 1: Two integers: T and N 

* Lines 2..T+1: Each line describes a trail as three space-separated integers. The first two integers are the landmarks between which the trail travels. The third integer is the length of the trail, range 1..100.

Output

* Line 1: A single integer, the minimum distance that Bessie must travel to get from landmark N to landmark 1.

Sample Input

5 5
1 2 20
2 3 30
3 4 20
4 5 20
1 5 100

Sample Output

90

Hint

INPUT DETAILS: 

There are five landmarks. 

OUTPUT DETAILS: 

Bessie can get home by following trails 4, 3, 2, and 1.

題目大意就是一頭牛從第N個路標到第一個路標,問最短距離是多少?

第一種方法: 迪傑斯特拉演算法

// <2> djk求單源最短路,鄰接矩陣存圖 時間複雜度o(n^2)
//#include <bits/stdc++.h>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int MAXN = (int) 1000+7;
const int INF = (int) 0x3f3f3f3f;

int n, m;   // n個點 m條邊
int mp[MAXN][MAXN];
void init( int n )  // 初始化
{
    for( int i=1; i<=n; i++ )
    {
        for( int j=1; j<=n; j++ )
        {
            if( i == j )
                mp[i][j] = 0;
            else
                mp[i][j] = mp[j][i] = INF;
        }
    }
}
void getmap( int m )    // 建圖
{
    int u, v, val;  // u 到 v 有一條權值為val的邊
    while( m-- )
    {
        scanf("%d%d%d", &u, &v, &val);
        mp[u][v] = mp[v][u] = min(mp[u][v], val);   //雙向邊,且防止有重邊
    }
}
bool vis[MAXN]; //  標記是否走過
int dis[MAXN];  // 起點到其他點的距離
void djk( int st, int ed )
{
    for( int i=1; i<=n; i++ )   // 初始化vis and dis
    {
        vis[i] = 0;
        dis[i] = mp[st][i];     //  初始所有dis都是起點到任一點的距離
    }
    vis[st] = 1;
    for( int i=2; i<=n; i++ )   // 只是迴圈 n-1 遍
    {
        int minum = INF, id = -1;
        for( int j = 1; j<=n; j++ )
        {
            if( !vis[j] && dis[j] < minum )
                minum = dis[j], id = j;
        }
        if( id == -1 ) break;   // 沒找到
        vis[id] = 1;
        for( int j=1; j<=n; j++ )   // 這個迴圈會更新,所有 起點通過距離他最短
        {                           // 的點到下一個點和它同時可以直接到達這個
            if( !vis[j] && mp[id][j] != INF )  // 點的距離,取兩種路徑的最小值
            {                       // 可以把已經找到最短路上的點看做一個集合
                if( dis[j] > dis[id] + mp[id][j] )  // 新的距離這個集合最短的
                    dis[j] = dis[id] + mp[id][j];   // 點看做中轉點,集合,中轉點
            }                       //  下一個未訪問過的點三者構成一個三角形
        }
    }
    printf("%d\n", dis[ed]);

}

int main()
{
    int t;
    scanf("%d%d", &t, &n);
    init(n);
    getmap(t);
    djk(n, 1);


    return 0;
}

 第二種方法: 優先佇列和鏈式前向星優化過的迪傑斯特拉

// <3> 優先佇列優化的djk求單源最短路,鏈式前向星存圖 時間複雜度o(E * log(V))
//#include <bits/stdc++.h>
#include <stdio.h>
#include <algorithm>
#include <queue>
#include <string.h>
using namespace std;
const int MAXN = (int) 1e5 + 7;
const int INF = (int) 0x3f3f3f3f;
typedef pair<int, int> Pair;

struct Edge
{
    int to, val, next;  // 成員變數, struct 預設public
    Edge(){};   // 建構函式
    Edge( int _to, int _val, int _next )
    {
        to = _to; val = _val; next = _next;
    }
};
struct Edge edge[MAXN << 1];    // 乘2

int n, m;   // n 是圖中的點數,m是圖中的邊數
int head[MAXN];     // head[i] 表示以i為起點最後一條邊的儲存位置
int cnt;            // 第cnt次輸入
void init( int n )  // 初始化
{
    memset(head, -1, sizeof(head));
    cnt = 0;
}
void add( int u, int v, int val )   // 鏈式前向星加邊函式, 加單向邊
{
    edge[cnt] = Edge(v, val, head[u]);
    head[u] = cnt++;
}
void getmap( int m )    // 建圖
{
    int u, v, val;
    while( m-- )
    {
        scanf("%d%d%d", &u, &v, &val);
        add(u, v, val);
        add(v, u, val);     // 如果是雙向圖,加上這一行
    }
}
int dis[MAXN];  // 距離
void djk( int st, int ed )
{
    memset(dis, 0x3f, sizeof(dis));
    priority_queue< Pair, vector<Pair>, greater<Pair> > q;  // 升序排列,但先取出的是較小的
    dis[st] = 0;
    q.push(make_pair(0, st));   // 第一個值是當前點到起點的距離
    while( !q.empty() )
    {
        Pair p = q.top();   // 每次取離起點最近的
        q.pop();
        int v = p.second;
        if( dis[v] < p.first )  continue;
        for( int i=head[v]; i != -1; i = edge[i].next ) // 鏈式前向星存圖方式的遍歷
        {
            Edge e = edge[i];
            if( dis[e.to] > dis[v] + e.val )
            {
                dis[e.to] = dis[v] + e.val;
                q.push(make_pair(dis[e.to], e.to));
            }
        }
    }
    printf("%d\n", dis[ed] == INF ? -1 : dis[ed]);
}
int main()
{
    int t;
    scanf("%d%d", &t, &n);
    init(n);
    getmap(t);
    djk(n, 1);

    return 0;
}

第三種方法: SPFA演算法


// <1> spfa求單源最短路,鏈式前向星存圖 時間複雜度o(kE) k是常數,大多數情況下為2
#include <stdio.h>
#include <queue>
#include <string.h>
using namespace std;
const int MAXN = (int) 1e5+11;
const int INF = (int) 0x3f3f3f3f;

struct Edge{
    int to, val, next;
    Edge(){}
    Edge( int _to, int _val, int _next )
    {
        to = _to; val = _val; next = _next;
    }
};
struct Edge edge[MAXN << 1];
int n, m;   // n 是圖中的點數,m是圖中的邊數
int head[MAXN], cnt;
void init( int n )
{
    memset(head, -1, sizeof(head));
    cnt = 0;
}
void add( int u, int v, int val )
{
    edge[cnt] = Edge(v, val, head[u]);
    head[u] = cnt++;
}
void getmap( int m )
{
    int u, v, val;
    while( m-- )
    {
        scanf("%d%d%d", &u, &v, &val);  // u->v有邊
        add(u, v, val);
        add(v, u, val);     // 如果是雙向圖,加上這個程式碼
    }
}
bool vis[MAXN];
int dis[MAXN];
void spfa( int st, int ed )
{
    memset(dis, 0x3f, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    queue<int> q;
    q.push(st);
    vis[st] = 1;   // 標記走過了
    dis[st] = 0;    // 起點到自身的距離為0
    while( !q.empty() )
    {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for( int i=head[u]; ~i; i = edge[i].next )
        {
            Edge e = edge[i];
            if( dis[e.to] > dis[u] + e.val )
            {
                dis[e.to] = dis[u] + e.val;
                if( !vis[e.to] )    // 訪問過的點不在放入佇列
                {
                    q.push(e.to);
                    vis[e.to] = 1;  // 標記走過了
                }
            }
        }
    }
    printf("%d\n", dis[ed] == INF ? -1 : dis[ed]);  // st到不了ed輸出-1
}


int main()
{
    int t;
    scanf("%d%d", &t, &n);
    init(n);
    getmap(t);
    spfa(n, 1);

    return 0;
}

第四種方法: floyd演算法

// <4> floyd求多源最短路,鄰接矩陣存圖 時間複雜度o(n^3),可求得任意兩個點之間的最短距離
#include <stdio.h>
#include <queue>
#include <string.h>
using namespace std;
const int MAXN = 1000+7;
const int INF = 0x3f3f3f3f;

int n, m;
int mp[MAXN][MAXN];
void init(int n)    // 初始化
{
    for( int i=1; i<=n; i++ )
    {
        for( int j=1; j<=n; j++ )
        {
            if( i == j )
                mp[i][j] = 0;
            else
                mp[i][j] = mp[j][i] = INF;
        }
    }
}
void getmap( int m )    // 建圖
{
    int u, v, val;
    while( m-- )
    {
        scanf("%d%d%d", &u, &v, &val);
        mp[u][v] = mp[v][u] = min(mp[u][v], val);   // 雙向圖寫這行
        //mp[u][v] = min(mp[u][v], val); // 單向圖寫著行
    }
}
void floyd( int n )
{
    for( int k=1; k<=n; k++ )   // k為三角形模型的中間點
    {
        for( int i=1; i<=n; i++ )
        {
            for( int j=1; j<=n; j++ )
                    mp[i][j] = min(mp[i][j], mp[i][k] + mp[k][j]); // dp
        }
    }
}
int main()
{
    int t;
    scanf("%d%d", &t, &n);
    init(n);
    getmap(t);
    floyd(n);
    printf("%d\n", mp[n][1] == INF ? -1 : mp[n][1]);

    return 0;
}