SQL 求最短路徑

研究過算法的朋友,應該都遇到過最短路徑求值的問題。簡單來講,就是從出發地到目的地有多條路線可走,要求使用算法找出最短路徑。算法

若是使用的是 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
相關文章
相關標籤/搜索