1. 程式人生 > >Acm之最短路問題演算法合集

Acm之最短路問題演算法合集

最短路問題常見有以下這幾種解法:

                  多源最短路:              1. Folyd                  (最容易實現)

                  單源最短路:              2. Dijkstra              (用點進行鬆弛)(文字與圖片來自啊哈演算法)

                                                    3. Bellman-Ford     (用邊進行鬆弛)

                                                    4. spfa演算法 (bellman-佇列優化)           (推薦


                                                    5. Bfs                        (只有每一步的代價一樣長的時候才適用)

                                                    6. A*                         (過於複雜,比賽幾乎用不到)

1. Floyd解法 —— 會超時,這裡也不講解(對每兩個點進行鬆弛操作)

2.    Dijkstra演算法


準備工作:  用鄰接矩陣map來儲存 圖,        

                    用 dis來儲存 起點到其餘各頂點的初始路程,

演算法流程:


程式碼實現步驟 1:

                minn = 1e9;
                for(int j = 0; j <= n; j++)     //左手抓出 集合外的最小點
                {   
                        if(!visited[j] && dis[j] < minn) // 保證了在集合p 之外,抓取離起點最近的新點
                        {   
                                minn = dis[j];
                                temp = j;
                        }   
                }   

                visited[temp] = 1;        //( 之前圖片中漏掉了) 抓取新點後應該將其標記為放入集合 P內, 即標記為舊點

程式碼實現步驟 2:

                        if( map[temp][j] < 1e9 && dis[temp] + map[temp][j] < dis[j])    //確保有路,且,然後鬆弛
                                dis[j] = dis[temp] + map[temp][j];

程式碼實現步驟 3:

 for(int j = 0; j <= n; j++)        //藉助 新點 對集合P內的每個元素(每個舊點) 進行鬆弛操作
                        if( map[temp][j] < 1e9 && dis[temp] + map[temp][j] < dis[j])    //確保有路,且,然後鬆弛
                                dis[j] = dis[temp] + map[temp][j];

程式碼實現步驟 4:

        for(int i = 0; i <= n; ++i)    //這個迴圈只起到 次數的作用, 執行 n 次, 左手會抓 n 次, 會鬆弛完所有點
        {   

                minn = 1e9;
                for(int j = 0; j <= n; j++)     //左手抓出 集合外的最小點
                {   
                        if(!visited[j] && dis[j] < minn)
                        {   
                                minn = dis[j];
                                temp = j;
                        }   
                }   

                visited[temp] = 1;  //放入 p 內

                for(int j = 0; j <= n; j++)            //用新點對集合內每個點進行鬆弛操作, 更新最短距離
                        if( map[temp][j] < 1e9 && dis[temp] + map[temp][j] < dis[j])
                                dis[j] = dis[temp] + map[temp][j];
        }   

完整程式碼:

void dijkstra()
{
        for(int i = 0; i <=n; ++i)
                dis[i] = map[0][i];
        memset(visited, 0, sizeof(visited));

        for(int i = 0; i <= n; ++i)
        {   
                minn = 1e9;
                for(int j = 0; j <= n; j++)     //左手抓出 集合外的最小點
                {   
                        if(!visited[j] && dis[j] < minn)
                        {   
                                minn = dis[j];
                                temp = j;
                        }   
                }   

                visited[temp] = 1;

                for(int j = 0; j <= n; j++)
                        if( map[temp][j] < 1e9 && dis[temp] + map[temp][j] < dis[j])
                                dis[j] = dis[temp] + map[temp][j];
        }   
}

鬆弛操作的通俗記憶:  新點繞路短則更

最後附上完整過程圖:


3.Bellman-Ford 的佇列優化 -- spfa(解決負邊權)

這裡我們不講原版的bellman演算法,直接講解其優化,省事。直到太多反而容易混

  首先來很搞清楚負邊權的問題

(一)為什麼最短路問題會有負邊權的存在?? 負邊權的作用是什麼??         

(未完待續)

(二)Dijstra 為什麼不能解決負邊權???


        由於第一點的選錯,便會導致後面所有的最短路都算錯。(Dp的子結構一層一層往上推)

下面我們來講解 Bellman-ford 的佇列演算法, 本質就是 DFS + bellman


            我們可以看出當一條路勁上     起點到其中一個點的路勁變短(鬆弛)之後,   後面的每個節點到起點的距離都

要發生變化,而這個節點 之前 的節點都不用更新。(圖中結論已給出)

            所以,我們用DFS的方式進行遍歷

準備階段: 假設起點到所有點的距離是 無窮


(圖中的 佇列 寫成了棧, 望海涵)

準備階段的實現:

void init()
{
        for(int i = 0; i < maxn; ++i)
                for(int j = 0; j < maxn; ++j)
                        if(i == j)  map[i][j] = 0;
                        else map[i][j] = inf;
        memset(vis, false, sizeof(vis));
        memset(money, false, sizeof(money));
        for(int i = 0; i < maxn; ++i)
                memset(cost[i], false, sizeof(cost[i]));
        fill( dis, dis+maxn, inf);
}


程式碼實現步驟1:

que.push( s ); vis[s] = true; dis[s]= 0;
程式碼實現步驟2, 3:
 int x = que.front();   que.pop();  vis[x] = false;
                for(int i  = 1; i <= n; ++i)
                {
                        if(map[x][i] < inf)//insure had road
                        {
                                if( dis[x]/*pre*/ + map[x][i]/*edge*/ < dis[i]/*direct*/)
                                {
                                        dis[i]  = dis[x] + map[x][i];
                                        money[i]  = money[x] + cost[x][i];
                                        if( !vis[i] )// if ont in queue, then push it in queue.
                                        {
                                                que.push( i );
                                                vis[i] = true;
                                        }
                                }
                        }
                }

完整程式碼:
void spfa()
{
        que.push( s ); vis[s] = true; dis[s]= 0;
        while( !que.empty() )
        {
                int x = que.front();   que.pop();  vis[x] = false;
                for(int i  = 1; i <= n; ++i)
                {
                        if(map[x][i] < inf)//insure had road
                        {
                                if( dis[x]/*pre*/ + map[x][i]/*edge*/ < dis[i]/*direct*/)
                                {
                                        dis[i]  = dis[x] + map[x][i];
                                        money[i]  = money[x] + cost[x][i];
                                        if( !vis[i] )// if ont in queue, then push it in queue.
                                        {
                                                que.push( i );
                                                vis[i] = true;
                                        }
                                }
                        }
                }
        }
}
通俗的記憶: 起點入列,鬆弛則後序入列Dijstra例題:   https://blog.csdn.net/pursue_my_life/article/details/80201722各類最短路演算法的比較: (以後補連結)