四大算法解決最短路徑問題(Dijkstra+Bellman-ford+SPFA+Floyd)


什么是最短路徑問題?

簡單來講,就是用於計算一個節點到其他所有節點的最短路徑。

單源最短路算法:已知起點,求到達其他點的最短路徑。

常用算法:Dijkstra算法、Bellman-ford算法、SPFA算法

多源最短路算法:求任意兩點之間的最短路徑。

常用算法:floyd算法

單源最短路徑——Dijkstra

Dijkstra算法是經典的最短路徑算法,用於計算一個節點到其他所有節點的最短路徑。

主要特點是以起始點為中心向外層層擴展,直到擴展到終點為止。

時間復雜度:O(n^2)

處理問題:單源、無負權、有向圖、無向圖最短路徑

不能使用的情況:邊中含有負權值(無法判斷)

#define INF 0x3f3f3f3f

int e[Max][Max];//e[i][j]代表從i->j的距離,不通設為無窮大
int dis[Max];//dis[i]代表從起點到i的最短距離
bool book[Max];//book[i]代表點i是否在S中
int n;//n個頂點
int s;//起點

void Dijkstra()
{
    for(int i=1;i<=n;i++)//初始化dis數組
        dis[i]=e[s][i];

    for(int i=1;i<=n;i++)//初始化book數組
        book[i]=0;
    dis[s]=0;
    book[s]=1;

    for(int i=1;i<=n-1;i++)//Dijkstra算法核心語句
    {
        int minDis=INF;
        int k;//找到與s最近的頂點k
        for(int j=1;j<=n;j++)
        {
            if(book[j]==0 && dis[j]<minDis)
            {
                minDis=dis[j];
                k=j;
            }
        }
        book[k]=1;

        for(int j=1;j<=n;j++)//“松弛”過程
        {
            if(e[k][j]<INF)
            {
                if(dis[j]>dis[k]+e[k][j])
                    dis[j]=dis[k]+e[k][j];
            }
        }
    }
}

基本思想:把帶權圖中所有的點分為兩部分S∪U,S為已經求出從起點到該點的最短路徑的點集合,U中為未確定最短路徑的點集合。把U中的點一個一個加入到S中,最后求出全部最短路徑。

如何把U中的點加入S中呢?

①初始時,S只包含源點s,即S={s},dis[s]=0。U包含除v外的其他頂點,即U={其余頂點},若s與U中頂點u有邊,則dis[u]=e[s][u],否則,dis[u]=∞。

②從U中找到一個與源點s距離最小(min(dis[]))的頂點k,把k加入S中,dis[k]確定(仔細想想,s與k最短路徑必定是dis[k]=e[s][k],找不到更短的)。

③以k為新考慮的中間點,修改源點s到U中各頂點的距離dis[]:若從源點s到頂點u的距離(dis[k]+e[k][u],經過頂點k)比原來距離(dis[u],不經過頂點k)短,則修改頂點u的距離值,修改后的距離值的頂點k的距離加上邊上的權。(這一過程稱為“松弛”)

④重復步驟②和③直到所有頂點都包含在S中。

 

算法優化:這里面每次都要尋找距離最短的那個點和距離,時間復雜度為O(n),可以用“堆”來優化,是時間復雜度降為O(lgn)。

 

算法過程詳解:http://ahalei.blog.51cto.com/4767671/1387799

 

單源最短路徑——Bellman-ford算法

求單源最短路徑,可以判斷有無負權回路(若有,則不存在最短路), 時效性較好,時間復雜度O(VE)。

處理問題:單源、可有負權、有向圖、無向圖最短路徑

注:下面代碼為有向圖最短路徑

#define INF 0x3f3f3f3f

struct Edge{
    int u;//
    int v;//
    int weight;//長度
};

Edge edge[maxm];//用來存儲所有的邊
int dis[maxn];//dis[i]表示源點到i的最短距離
int n,m;//n個點,m條邊
int s;//源點

bool Bellmen_ford()
{
    for(int i=1;i<=n;i++)//初始化
        dis[i]=INF;

    dis[s]=0;//源節點到自己的距離為0

    for(int i=1;i<n;i++)//松弛過程,計算最短路徑
    {
        for(int j=1;j<=m;j++)
        {
            if(dis[edge[j].v]>dis[edge[j].u]+edge[j].weight)//比較s->v與s->u->v大小
                dis[edge[j].v]=dis[edge[j].u]+edge[j].weight;
        }
    }

    for(int j=1;j<=m;j++)//判斷是否有負邊權的邊
    {
        if(dis[edge[j].v]>dis[edge[j].u]+edge[j].weight)
            return false;
    }

    return true;
}

基本思想:bellman-ford的思想和dijkstra很像,其關鍵點都在於不斷地對邊進行松弛。而最大的區別就在於前者能作用於負邊權的情況。其實現思路是在求出最短路徑后,判斷此刻是否還能對便進行松弛,如果還能進行松弛,便說明還有負邊權的邊。

 

單源最短路徑——SPFA算法

上一種算法其實不好用,復雜度太高,SPFA算法是Bellman-ford算法的隊列優化,比較常用。SPFA算法在負邊權圖上可以完全取代Bellman-ford算法,另外在稀疏圖中也表現良好。但是在非負邊權圖中,為了避免最壞情況的出現,通常使用效率更加穩定的Dijkstra算法,以及它的使用堆優化的版本。通常的SPFA算法在一類網格圖中的表現不盡如人意。不是很穩定,不如Dijkstra。

處理問題:單源、可有負權、有向圖、無向圖最短路徑(自身其實無法處理負權)

#define INF 0x3f3f3f3f

int dis[MAX];//dis[i]表示起點到i的最短距離
bool vis[MAX];//是否訪問過點i
int e[MAX][MAX];//矩陣

int n,m;//點和邊的數量
int s;//源點

void SPFA()
{
    for(int i=1;i<=n;i++)//初始化
    {
        dis[i]=INF;
        vis[i]=false;
    }
    queue<int> q;
    q.push(s);
    dis[s]=0;
    vis[s]=true;

    while(!q.empty())
    {
        int cur=q.front();
        q.pop();
        vis[cur]=false;
        for(int i=1;i<=n;i++)
        {
            if(e[cur][i]!=INF&&dis[i]>=dis[cur]+e[cur][i])
            {
                dis[i]=dis[cur]+e[cur][i];
                if(!vis[i])
                {
                    vis[i]=true;
                    q.push(i);
                }
            }
        }
    }
}

算法思想:

設立一個隊列用來保存待優化的點,優化時每次取出隊首結點u,並且用u點當前的最短路徑估計值對u點所指向的結點v進行松弛操作,如果v點的最短路徑估計值有所調整,且v點不在當前的隊列中,就將v點放入隊尾。這樣不斷從隊列中取出結點來進行松弛操作,直至隊列空為止。

 

算法過程詳解:http://www.360doc.com/content/13/1208/22/14357424_335569176.shtml

例題:http://ac.jobdu.com/problem.php?pid=1008

 

多源最短路徑——Floyd算法

Floyd算法是一種利用動態規划思想的計算加權圖中多源點之間最短路徑的算法。可以正確處理有向圖或負權的最短路徑問題。

時間復雜度:O(N^3)

空間復雜度:O(N^2)

處理問題:多源、可有負權、有向圖、無向圖最短路徑

int e[Max][Max];//e[i][j]代表從i->j的距離,不通設為無窮大
int n;//n個頂點
//Floyd算法
void Floyd()
{
    for(int k=1;k<=n;k++)//遍歷所有的中間點
 { for(int i=1;i<=n;i++)//遍歷所有的起點  { for(int j=1;j<=n;j++)//遍歷所有的終點  { if (e[i][j]>e[i][k]+e[k][j])//如果當前i->j的距離大於i->k->j的距離之和 e[i][j]=e[i][k]+e[k][j];//更新從i->j的最短路徑  } } } }

算法思想:

①如果不允許有中轉點,那么最短路徑就是我們的e[][]原始矩陣;

②現在只允許經過1號頂點進行中轉,判斷e[i][1]+e[1][j]是否比e[i][j]要小,修改e[][];

③接下來只允許經過1和2號頂點進行中轉……

④最后,允許經過1~n號所有頂點進行中轉,得到最后的e[][],就是要求的任意兩點之間的最短路程。

這里面是動態規划思想的體現。狀態轉移方程:e[i,j]=max{e[i,k]+e[k,j],e[i,j]};

算法過程:對於每一對頂點 i 和 j,看看是否存在一個頂點 k 使得從 i 到 k 再到 j 比已知的路徑更短。如果是,則更新它。

算法過程詳解:http://ahalei.blog.51cto.com/4767671/1383613

 

另外,特別鳴謝,本文參考(含大量例題):http://blog.csdn.net/hjd_love_zzt/article/details/26739593

 

作者: AlvinZH

出處: http://www.cnblogs.com/AlvinZH/

本人Github:https://github.com/Pacsiy/JobDu

本文版權歸作者AlvinZH和博客園所有,歡迎轉載和商用,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利.


免責聲明!

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



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