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各類最短路演算法的比較: (以後補連結)