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
相关文章
相关标签/搜索