【C/C++】最短路徑


BFS求無權圖的最短路徑

用book數組的值表示路徑長度即可,省略

Floyd算法(允許負邊)

  • Floyd算法可以一次性求出所有節點之間的最短距離,且代碼簡單,但是時間復雜度達到了n^3,因此只適用於n<200的情況;
  • 原理:任意兩點i,j之間的距離分為兩種情況:過k點和不過k點。從k=1開始操作遍歷到n即可,不過很顯然每次計算基本上只有對k的鄰邊是有效的
  • 代碼實現(基於鄰接矩陣):
#include<bits/stdc++.h>

using namespace std;

const int INF = 0x3f3f3f3f;
const int MAXN = 105;
int graph[MAXN][MAXN];

void floyd(int n)
{
    int s=1;//求s到n的距離
    for(int k=1; k<=n; k++)
        for(int i=1; i<=n; i++)
            if(graph[i][k] != INF)//如果相等則說明無需遍歷
                for(int j=1; j<=n; j++)
                    if(graph[i][j] > graph[i][k] + graph[k][j])
                        graph[i][j] = graph[i][k] + graph[k][j];
    printf("%d\n",graph[s][n]);//輸出結果
    return ;
}

int main()
{
    int n,m;
    while(~scanf("%d %d",&n,&m))
    {
        if(n==0 && m==0) break;
        memset(graph,0x3f,sizeof(graph));
        while(m--)
        {
            int a,b,c;
            scanf("%d %d %d",&a,&b,&c);
            graph[a][b] = graph[b][a] = c;
        }
        floyd(n);
    }
    return 0;
}
  • Floyd算法適用於鄰接矩陣:由於算法是動態規划的思想,必須有一個二維數組表示點與點之間的距離,所以使用其他圖的表示方法會浪費空間;
  • 判斷負圈:通過代碼不難發現,點 i 到自己的距離並非為0,而是graph[i][i] = graph[i][k] + graph[k][i],即繞外面一圈再回來。一旦存在graph[i][i]<0,就說明有負圈存在;

Bellman-Ford算法(允許負邊)

  • Bellman-Ford算法解決的是單源最短路徑問題,即起點s到圖中每個點的最短距離;
  • 原理:每一輪更新中,對於每一個點,詢問它的鄰居是否可以到達s點:如果可以,當前的點就可以通過鄰居到達s點。不難想象,每一輪更新中至少有一個點到s的最短距離可以被確定下來,所以一共需要更新n輪,時間復雜度為O(n*m);
  • 圖的表示方式:如果使用鄰接矩陣,遍歷邊的過程依然為O(n*n),並沒有得到優化,因此使用數組存邊或者鄰接表來存圖;
  • 代碼實現:
#include<bits/stdc++.h>

using namespace std;

const int INF = 0x3f3f3f3f;
const int MAXN = 5e3 + 10;
struct edge{int u, w, v; } e[MAXN*2];
int pre[MAXN],d[MAXN];//pre存放前置節點,d[i]存放s到點i的距離

void Print_path(int s, int t)//打印s到t的最短路徑
{
    if(s == t) printf("%d",s);
    else 
    {
        Print_path(s,pre[t]);
        printf(" %d",t);
    }
}

void bellman(int n, int cnt)
{
    int s = 1;//s為起點
    memset(d,0x3f,sizeof(d));//初始化為最大值
    d[s] = 0;//s到自己的距離為0
    for(int k=1; k<=n; k++)
        for(int i=0; i<cnt; i++)
        {
            int x = e[i].u, y = e[i].v;
            if(d[x] > d[y] + e[i].w)
            {
                d[x] = d[y] + e[i].w;
                pre[x] = y;
            }
        }
    printf("%d\n",d[n]);//打印s到n的最短距離
    Print_path(s,n);
    printf("\n");
}

int main()
{
    int n,m;
    while(~scanf("%d %d",&n,&m))
    {
        int cnt = 0;
        while(m--)
        {
            int a,b,c;
            scanf("%d %d %d",&a,&b,&c);
            e[cnt].u = a; e[cnt].v = b; e[cnt].w = c; cnt++;
            e[cnt].v = a; e[cnt].u = b; e[cnt].w = c; cnt++;
        }
        bellman(n,cnt);
    }
    return 0;
}
  • 判斷負圈:很明顯當負圈存在時,程序會一直存在距離更新,因此要判斷循環次數是否超過了n,如果超過說明有負圈;
  • 優化后的代碼:
bool bellman(int n, int cnt)
{
    int s = 1;//s為起點
    memset(d,0x3f,sizeof(d));//初始化為最大值
    d[s] = 0;//s到自己的距離為0
    bool updata = true;//updata表示上一輪有沒有更新,如果有則繼續更新,否則停止更新
    int k = 0;//k表示循環次數
    while(updata)
    {
        updata = false;//當前一輪還未進行過更新
        k++;
        if(k>n) return false;//循環次數超過n則返回有負圈
        for(int i=0; i<cnt; i++)
        {
            int x = e[i].u, y = e[i].v;
            if(d[x] > d[y] + e[i].w)
            {
                updata = true;//發生了更新操作
                d[x] = d[y] + e[i].w;
                pre[x] = y;
            }
        }
    }
    printf("%d\n",d[n]);//打印s到n的最短距離
    Print_path(s,n);
    printf("\n");
    return true;
}

SPFA算法(允許負邊)

  • SPFA是對Bellman-Ford算法的優化。在Bellman-Floyd算法每一輪的更新中,如何確定當前節點v需不需要更新?很明顯,當且僅當v的鄰居節點的最短路徑發生變動的時候,節點v才需要更新。SPFA算法使用BFS的思想,把需要更新的節點放進隊列中,當隊列為空時,算法結束。
  • 判斷負圈:節點每進入一次隊列即為當前節點更新了一次,由Bellman-Ford算法中的結論可知,當存在一個節點更新次數超過n次時,說明一定有負圈。
  • 代碼實現(:
//基於鄰接表
#include<bits/stdc++.h>

using namespace std;

const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 10;

struct edge
{
    int to,w;
};

vector<edge>e[MAXN];
int pre[MAXN];//記錄路徑
bool inq[MAXN];//是否在隊列內,優化用
int Neg[MAXN];//記錄循環次數判斷負圈
int dis[MAXN];//記錄最短距離

void print_path(int s, int t)//遞歸輸出最短路徑
{
        if(s == t) {printf("%d",s); return ;}
        print_path(s, pre[t]);
        printf(" %d",t);
}

void print_path2(int s, int t)//非遞歸的路徑輸出,適用於極端情況
{
        stack<int>ans;
        while(s!=t)
        {
            ans.push(t);
            t = pre[t];
        }
        printf("%d",s);
        while(!ans.empty())
        {
            printf(" %d",ans.top());
            ans.pop();
        }
        printf("\n");
}

bool spfa(int s, int n)
{
    memset(dis,0x3f,sizeof(dis));
    memset(inq,false,sizeof(inq));
    memset(Neg,0,sizeof(Neg));
    Neg[s] = 1;
    inq[s] = true;
    dis[s] = 0;
    queue<int>Q;
    Q.push(s);
    while(!Q.empty())
    {
        int u = Q.front();
        Q.pop();
        inq[u] = false;
        for(int i=0; i<e[u].size(); i++)
        {
            int v = e[u][i].to;
            int w = e[u][i].w;
            if(dis[v] > dis[u] + w)
            {
                dis[v] = dis[u] + w;
                pre[v] = u;
                if(!inq[v]) 
                {
                    inq[v] = true;
                    Q.push(v);
                    Neg[v]++;
                    if(Neg[v] > n) return false;//返回值為false代表有負圈
                }

            }
        }
    }
    printf("%d\n",dis[n]);
    print_path2(s,n);
    return true;
}

int main()
{
    int n,m;
    while(~scanf("%d %d",&n,&m))
    {
        for(int i=0; i<m; i++)
        {
            int a,b,c;
            scanf("%d %d %d",&a,&b,&c);
            edge t = {b,c};
            e[a].push_back(t);
        }
        spfa(1,n);
    }
    return 0;
}
//基於鏈式前向星的SPFA算法
#include<bits/stdc++.h>

using namespace std;

const int INF = 0x3f3f3f3f;
const int NUM = 1e6 + 10;

struct Edge
{
    int to,next,w;
}edge[NUM];
int cnt;
int head[NUM];
int dis[NUM];
bool inq[NUM];
int Neg[NUM];
int pre[NUM];

void print_path(int s,int t)//打印起點s到t的最短路徑
{
    stack<int>ans;
    while(t!=s)
    {
        ans.push(t);
        t = pre[t];
    }
    printf("%d",s);
    while(!ans.empty())
    {
        printf(" %d",ans.top());
        ans.pop();
    }
    printf("\n");
}

void init()//前向星的初始化
{
    for(int i=0; i<NUM; i++)
    {
        edge[i].next = -1;
        head[i] = -1;
    }
    cnt = 0;
}

void addedge(int u, int v, int w)//前向星的加邊操作
{
    edge[cnt].to = v;
    edge[cnt].w  = w;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}

bool spfa(int s, int n)
{
    memset(inq,false,sizeof(inq));
    memset(dis,0x3f,sizeof(dis));
    memset(Neg,0,sizeof(Neg));
    Neg[s] = 1;
    dis[s] = 0;
    inq[s] = true;
    queue<int>Q;
    Q.push(s);
    while(!Q.empty())
    {
        int u = Q.front(); Q.pop();
        inq[u] = false;
        for(int i=head[u]; i!=-1; i=edge[i].next)
        {
            int v = edge[i].to; int w = edge[i].w;
            if(dis[v] > dis[u] + w)
            {
                dis[v] = dis[u] + w;
                pre[v] = u;
                if(!inq[v])
                {
                    Q.push(v);
                    inq[v] = true;
                    Neg[v]++;
                    if(Neg[v] > n) return false;
                }
            }
        }
    }
    printf("%d\n",dis[n]);
    print_path(s, n);
    return true;
}

int main()
{
    int n,m;
    while(~scanf("%d %d",&n,&m))
    {
        init();
        while(m--)
        {
            int a,b,c;
            scanf("%d %d %d",&a,&b,&c);
            addedge(a, b, c);
        }
        spfa(1, n);
    }
    return 0;
}

Dijkstra算法(無法求負邊)

  • Dijkstra算法應用了貪心的思想,即從起點開始抄近路走。類似於多米諾骨牌,即從起點開始推倒骨牌,當節點第一次被到達時最短路徑被確定。
  • 代碼借助STL中的優先隊列完成,每次取出到S距離最短的節點來模擬多米諾骨牌模型。
//基於鄰接表
#include<bits/stdc++.h>

using namespace std;

const int INF = 0x3f3f3f3f;
const int NUM = 1e5;

struct edge
{
    int from, to, w;
    edge(int a, int b, int c)
    {
        from = a;
        to = b;
        w = c;
    }
};
vector<edge>e[NUM];

struct s_node
{
    int id, n_dis;
    s_node(int b, int c)
    {
        id = b;
        n_dis = c;
    }
    bool operator < (const s_node & a) const
    {
        return n_dis > a.n_dis;
    }
};
int dis[NUM];
bool done[NUM];

int pre[NUM];//記錄前驅結點
void print_path(int s, int t)
{
    stack<int>ans;
    while(s != t)
    {
        ans.push(t);
        t = pre[t];
    }
    printf("%d",s);
    while(!ans.empty())
    {
        printf(" %d",ans.top());
        ans.pop();
    }
    printf("\n");
}

void dijkstra(int s,int n)
{
    memset(dis, 0x3f, sizeof(dis));
    memset(done, false, sizeof(done));
    dis[s] = 0;
    priority_queue<s_node>Q;
    Q.push(s_node(s, dis[s]));
    while(!Q.empty())
    {
        s_node u = Q.top();
        Q.pop();
        if(done[u.id])
            continue;
        done[u.id] = true;
        for(int i=0; i<e[u.id].size(); i++)
        {
            edge y = e[u.id][i];
            if(done[y.to])
                continue;
            if(dis[y.to] > y.w + u.n_dis)
            {
                dis[y.to] = y.w + u.n_dis;
                Q.push(s_node(y.to, dis[y.to]));
                pre[y.to] = u.id;
            }
        }
    }
    printf("%d\n",dis[n]);
    print_path(s, n);
}

int main()
{
    int n,m;
    while(~scanf("%d %d",&n,&m))
    {
        if(n==0 && m==0) break;
        for(int i=1; i<=n; i++)
            e[i].clear();
        while(m--)
        {
            int a,b,c;
            scanf("%d %d %d",&a,&b,&c);
            e[a].push_back(edge(a,b,c));
            e[b].push_back(edge(b,a,c));
        }
        dijkstra(1, n);
    }
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM