最短路徑——SPFA算法


一、前提引入

我們學過了Bellman-Ford算法,現在又要提出這個SPFA算法,為什么呢?

考慮一個隨機圖(點和邊隨機生成),除了已確定最短路的頂點與尚未確定最短路的頂點之間的邊,其它的邊所做的都是無用的,大致描述為下圖(分割線以左為已確定最短路的頂點):

其中紅色部分為所做無用的邊,藍色部分為實際有用的邊。既然只需用到中間藍色部分的邊,那就是SPFA算法的優勢之處了。

 

二、算法描述

算法特點:在 Bellman-ford 算法的基礎上加上一個隊列優化,減少了冗余的松弛操作,是一種高效的最短路算法。

時間復雜度:O(mn)

關鍵詞:初始化    松弛操作    隊列

主要變量如下:

int n              表示有n個點,從1~n標號

int s,t            s為源點,t為終點

int dis[N]           dis[i]表示源點s到點i的最短路徑

int pre[N]          記錄路徑,pre[i]表示i的前驅結點

bool vis[N]          vis[i]=true表示點i在隊列中

queue<int> q     隊列,在整個算法中有頂點入隊了要記得標記vis數組,有頂點出隊了記得消除那個標記

【初始化】

dis數組全部賦值為INF,pre數組全部賦值為-1(表示還不知道前驅),

dis[s] = 0 表示源點不要求最短路徑(或者最短路徑就是0)。

【隊列+松弛操作】

讀取隊頭頂點u,並將隊頭頂點u出隊(記得消除標記);將與點u相連所有點v進行松弛操作,如果能更新估計值(即令d[v]變小),那么就更新,另外,如果點v沒有在隊列中,那么要將點v入隊(記得標記),如果已經在隊列中了,那么就不用入隊,這樣不斷從隊列中取出頂點來進行松弛操作。

以此循環,直到隊空為止就完成了單源最短路的求解。

【算法過程】

設立一個隊列用來保存待優化的頂點,優化時每次取出隊首頂點 u,並且用 u 點當前的最短路徑估計值dis[u]對與 u 點鄰接的頂點 v 進行松弛操作,如果 v 點的最短路徑估計值dis[v]可以更小, v 點不在當前的隊列中,就將 v 點放入隊尾。這樣不斷從隊列中取出頂點來進行松弛操作,直至隊列空為止。

【檢測負權回路】

方法:如果某個點進入隊列的次數大於等於 n,則存在負權回路,其中 n 為圖的頂點數。

說明:SPFA無法處理帶負環的圖。

 

三、代碼實現

#include<iostream>    
#include<queue>
#include<stack>
using namespace std;

int matrix[100][100];  //鄰接矩陣
bool visited[100];     //標記數組
int dist[100];         //源點到頂點i的最短距離
int path[100];         //記錄最短路的路徑
int enqueue_num[100];  //記錄入隊次數
int vertex_num;        //頂點數
int edge_num;          //邊數
int source;            //源點

bool SPFA()
{
    memset(visited, 0, sizeof(visited));
    memset(enqueue_num, 0, sizeof(enqueue_num));
    for (int i = 0; i < vertex_num; i++)
    {
        dist[i] = INT_MAX;
        path[i] = source;
    }

    queue<int> Q;
    Q.push(source);
    dist[source] = 0;
    visited[source] = 1;
    enqueue_num[source]++;
    while (!Q.empty())
    {
        int u = Q.front();
        Q.pop();
        visited[u] = 0;
        for (int v = 0; v < vertex_num; v++)
        {
            if (matrix[u][v] != INT_MAX)  //u與v直接鄰接
            {
                if (dist[u] + matrix[u][v] < dist[v])
                {
                    dist[v] = dist[u] + matrix[u][v];
                    path[v] = u;
                    if (!visited[v])
                    {
                        Q.push(v);
                        enqueue_num[v]++;
                        if (enqueue_num[v] >= vertex_num)
                            return false;
                        visited[v] = 1;
                    }
                }
            }
        }
    }
    return true;
}

void Print()
{
    for (int i = 0; i < vertex_num; i++)
    {
        if (i != source)
        {
            int p = i;
            stack<int> s;
            cout << "頂點 " << source << " 到頂點 " << p << " 的最短路徑是: ";

            while (source != p)  //路徑順序是逆向的,所以先保存到棧
            {
                s.push(p);
                p = path[p];
            }

            cout << source;
            while (!s.empty())  //依次從棧中取出的才是正序路徑
            {
                cout << "--" << s.top();
                s.pop();
            }
            cout << "    最短路徑長度是:" << dist[i] << endl;
        }
    }
}

int main()
{

    cout << "請輸入圖的頂點數,邊數,源點:";
    cin >> vertex_num >> edge_num >> source;

    for (int i = 0; i < vertex_num; i++)
        for (int j = 0; j < vertex_num; j++)
            matrix[i][j] = INT_MAX;  //初始化matrix數組

    cout << "請輸入" << edge_num << "條邊的信息:\n";
    int u, v, w;
    for (int i = 0; i < edge_num; i++)
    {
        cin >> u >> v >> w;
        matrix[u][v] = w;
    }

    if (SPFA())
        Print();
    else
        cout << "Sorry,it have negative circle!\n";

    return 0;
}

 運行如下:


免責聲明!

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



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