1. 程式人生 > >圖論 最短路 SPFA + 前向星存邊

圖論 最短路 SPFA + 前向星存邊

SPFA模板題

  • 介紹
    ====
  • SPFA

SPFA已死

SPFA是基於Bellman - Ford的一種賊快的演算法, 用佇列來實現。
通常用於求含負權邊的單源最短路徑,以及判負權環。
SPFA 最壞情況下複雜度和樸素 Bellman-Ford 相同,為 O(VE)。
(參考百度百科)

毒瘤資料免談

  • 前向星

一個數據結構,裡面的成員可以儲存起點,終點和權值。
要有一個數組維護每點連出去的邊的起點。

1.鏈式前向星構造

這裡我們使用的是鏈式前向星,用結構體儲存每一條邊:

struct Edge
{
	 //其實還可以記錄這條邊的起點,但這題沒必要
int next; //表示這條邊所指向的下一條邊 int to; //這條邊的終點 int w; //權值 };

再開個first陣列

first[i] 表示以i為起點的第一條邊

2.加邊

first[i]其實就是以i為起點的邊所組成的連結串列的第一個元素。

每次加邊,我們讓first[i]這條邊指向要加的邊,再將所加的邊更新為first[i]。

因此,我們每次遍歷是倒著遍歷的:

void AddEdge(int begin, int end, int w)
{
	len++; //第len條邊
	e[len] = Edge{first[begin]
, end, w}; //first[begin]是當前所加的邊指向的下一條邊 first[begin] = len; //first是儲存下標的 };

3.遍歷

從第一條邊開始,e[i].[next] 即為下一條邊的編號,從而遍歷以s為起點的所有邊,當邊的編號為0時即跳出迴圈

for (int i = first[s]; i; i = e[i].next)
  • 演算法
    ======
    這裡寫圖片描述

思想:
我們先將起點入隊, 將所有與起點相連的點進行鬆弛操作。
如果鬆弛成功的點不在佇列裡,就把它進隊。
最後隊空即結束。

上程式碼:

#include
<bits/stdc++.h>
#define INF 2147483647 #define MAXN 10000 #define MAXM 500000 using namespace std; struct Edge { //前向星存邊 int next, to, w; //next:下一條邊, to, w:此邊終點和此邊權值 }e[500030]; int n, m, s, x, y, w, len; int first[MAXN + 30], d[MAXN + 30], vis[MAXN + 30]; //d[i]:起點到i的最短路, first[i]:i的第一條出邊, vis[i]:記錄i是否在佇列裡 void AddEdge(int a, int b, int v) { //加邊 len++; e[len] = Edge{first[a], b, v}; first[a] = len; } void SPFA() { for (int i = 1; i <= n; ++i) d[i] = INF; d[s] = 0; queue < int > q; q.push(s); vis[s] = 1; //初始化 while (!q.empty() ) { x = q.front(); q.pop(); vis[x] = 0; for (int i = first[x]; i != 0; i = e[i].next) { //前向星遍歷 y = e[i].to; if (d[x] + e[i].w < d[y]) { // 鬆弛 d[y] = d[x] + e[i].w; if (vis[y] == 0){ q.push(y); vis[y] = 1; //標記在佇列裡 } } } } } int main() { scanf ("%d%d%d", &n, &m, &s); for (int i = 1; i <= m; ++i) { scanf ("%d%d%d", &x, &y, &w); AddEdge(x, y, w); } SPFA(); for (int i = 1; i <= n; ++i) printf("%d ", d[i]); printf("\n"); return 0; }

撒花結束