研究過算法的朋友,應該都遇到過最短路徑求值的問題。簡單來講,就是從出發地到目的地有多條路線可走,要求使用算法找出最短路徑。算法
若是使用的是 SQL ,怎麼解決這類問題?sql
接着往下看,很快就有答案了。code
先看示例表,dist 存儲了目的地到出發地的距離,咱們要計算出從 a 地出發到其它地點的最短距離。遞歸
sp ep distance ------ ------ ---------- a b 5 a c 1 b c 2 b d 1 c d 4 c e 8 d e 3 d f 6
因爲要窮舉全部可能的路線,所以使用遞歸是最簡單的解決方案。在 SQL 中用遞歸請參考——SQL 的遞歸表達式。get
在遞歸表達式中,初始的數據應該是列舉出能從 a 點直接到達的地點及相應的距離,目前有 a -> b、a -> c 這兩條路線。it
SELECT *, CAST(CONCAT(a.sp, ' -> ', a.ep) AS CHAR(100)) AS path FROM dist a WHERE sp = 'a'
字段 path 記錄了從 a 點出發到其它地點的距離。class
對於 a 點不能直接到達的地點,可經過直接到達的點再轉到目的地。好比,從 a -> d 的路線就有 a -> b -> d 和 a -> c -> d 兩條。循環
經過下面的 SQL 可窮舉出全部路線:qq
WITH RECURSIVE t (sp, ep, distance, path) AS (SELECT *, CAST(CONCAT(a.sp, ' -> ', a.ep) AS CHAR(100)) AS path FROM dist a WHERE sp = 'a' UNION ALL SELECT t.sp, b.ep,-- 當前路線的目的地就是下一個目的地的出發點 t.distance + b.distance,-- 當前路線的距離之和 CAST( CONCAT(t.path, ' -> ', b.ep) AS CHAR(100) ) AS path FROM t INNER JOIN dist b ON b.sp = t.ep AND INSTR(t.path, b.ep) <= 0) SELECT * FROM t
對於 「a -> b」 這條路線而言,b 是 a 要到達的目的地,假如這條路線加入了 d ,變成 「a -> b -> d」,那麼 b 就成了到達 d 前的出發點。數據
在 SQL 中加入了條件 INSTR(t.path, b.ep) <= 0
, 主要是防止有環路而出現死循環。不過,在咱們的數據中沒有環路,不加這個條件也沒有任何問題。
上面 SQL 的輸出 >>>
sp ep distance path ------ ------ -------- ---------------------- a b 5 a -> b a c 1 a -> c a c 7 a -> b -> c a d 6 a -> b -> d a d 5 a -> c -> d a e 9 a -> c -> e a d 11 a -> b -> c -> d a e 15 a -> b -> c -> e a e 8 a -> c -> d -> e a e 9 a -> b -> d -> e a f 11 a -> c -> d -> f a f 12 a -> b -> d -> f a e 14 a -> b -> c -> d -> e a f 17 a -> b -> c -> d -> f
從 a 出發到其它地點有可能存在多條路線,若是咱們只要找出最短路線的距離,SQL 能夠這麼寫:
SELECT sp, ep, MIN(distance) AS distance FROM t GROUP BY sp, ep; sp ep distance ------ ------ ---------- a b 5 a c 1 a d 5 a e 8 a f 11
最好是能把最短距離對應的路線也給展現出來,稍微作一點調整。完整的 SQL 以下:
WITH RECURSIVE t (sp, ep, distance, path) AS (SELECT *, CAST(CONCAT(a.sp, ' -> ', a.ep) AS CHAR(100)) AS path FROM dist a WHERE sp = 'a' UNION ALL SELECT t.sp, b.ep, t.distance + b.distance, CAST( CONCAT(t.path, ' -> ', b.ep) AS CHAR(100) ) AS path FROM t INNER JOIN dist b ON b.sp = t.ep AND INSTR(t.path, b.ep) <= 0), t1 AS (SELECT *, row_number () over ( PARTITION BY sp, ep ORDER BY distance ) AS rn FROM t) SELECT sp, ep, path, distance FROM t1 WHERE rn = 1
最終的結果 >>>
sp ep path distance ------ ------ --------------- ---------- a b a -> b 5 a c a -> c 1 a d a -> c -> d 5 a e a -> c -> d -> e 8 a f a -> c -> d -> f 11