原创

MADlib——基于SQL的数据挖掘解决方案(28)——图算法之单源最短路径

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://wxy0327.blog.csdn.net/article/details/79564814

        图算法指利用特制的线条算图求得答案的一种简便算法。无向图、有向图和网络能运用很多常用的图算法,其中主要包括各种遍历算法(这些遍历类似于树的遍历),寻找最短路径的算法,寻找网络中最低代价路径的算法。这些算法常被用以回答一些与图相关的问题,诸如图是否是连通的,图中两个顶点间的最短路径是什么等等。在数据挖掘领域中,图算法可应用到多种场合,以解决特定问题,如管道优化、路由选择、快递服务、网站通信等。

        本篇介绍图的基本概念和表示方法,并简要说明一些常用的图算法。MADlib 1.10.0 文档中只列出了一种图算法模型,即单源最短路径,因此我们将详细描述该算法及其相关函数。同样也会用一个简单示例,说明MADlib单源最短路径函数的用法。

一、图算法简介

1. 图的概念

        在计算中,常将运算方程或实验结果绘制成由若干有标尺的线条所组成的图,称为“算图”。计算时根据已知条件,从有关线段上一点开始,连结相关线段上的点,连线与表示所求量线段的交点即为答案。图算法是对树的拓展,树是自上而下的数据结构,除根节点外,其它每个节点都有一个父节点,从上向下排列。而图没有了父子节点的概念,图中的节点都是平等关系,结果更加复杂。

定义图 ,其中 代表顶点Vertex, 代表边Edge,一条边就是一个定点对 ,其中 。图分有向图和无向图。在无向图中,如果  (表示 u 到 v 的路径)联通,那么  也联通,例如“1”到“2”联通,“2”到“1”也联通。但是在有向图中“1”到“2”联通,但是“2”到“1”是不联通的。图1与图2分别表示一个无向图和一个有向图。


               图1 无向图                                图2 有向图                       

        在图的概念中,除了顶点和边的概念外,还经常涉及到权值,表示一个顶点到另一个顶点的“代价”,如果顶点不联通,可以认为权值无限大。如果不涉及权值,那么可以认为联通的顶点权值都为1。

2. 图的表示

        数据结构中经常用邻接表和邻接矩阵表示图。

(1)邻接表

        图3即为图2所示有向图的邻接表,表中的一个节点对应图中的一个顶点,节点后面的链表是与这个节点联通的节点。

图3 邻接表

        邻接表常用于表示稀疏图,即节点的边数  远小于 。对于有向图,邻接表存储所占空间为 ,对于无向图为 ,因为每条边在邻接表中出现两次。邻接表在存储上占优势,但是在判断两个节点  是否联通时,要首先在邻接表中找到 u,然后再遍历 u 后面的链表。

(2)邻接矩阵

        图4是图1所示无向图的邻接矩阵表示。邻接矩阵是一个的矩阵 ,如果  联通,那么 。如果图是加权的话,

图4 邻接矩阵

        可以看出,邻接矩阵表示方法所占空间为 ,但是在判断两个节点是否联通时,只需 。当图比较小时更多采用邻接矩阵,因为它更明了。如果图没有加权,可以用一个二进制位来表示两个图是否联通。

3.常用图算法

(1)图的遍历

        图的遍历是指从图中的任一顶点出发,对图中的所有顶点访问一次且只访问一次。遍历操作是图的一种基本操作,图的许多操作都建立在遍历的基础之上。在遍历图时,为保证图中各顶点在遍历过程中被访问且仅一次,需要为每个顶点设计一个访问标记,设置一个数组,用于标识图中哪个顶点被访问过。数组元素的初始值全部为0,表示顶点均未被访问过。某个顶点被访问后,将相应访问标志数组中的值设为1,以表示该顶点已经被访问。通常图的遍历有两种:深度优先遍历搜索和广度优先遍历搜索。

        深度优先遍历是尽可能“深"的遍历图。假设从节点 开始遍历,遍历与  联通的且未被遍历过的节点 ,再遍历与 联通的且未被遍历过的节点 。如果遍历到 后无节点可以遍历,那么退回到  再去找节点遍历,以此类推,直到图中所有节点都被遍历过。可以看出图的深度优先遍历可以借助堆栈实现。

  1. 把节点 v 放入堆栈,标记 v 。
  2. 若堆栈为空则结束,否则取出栈顶节点 u 。
  3. 找出与 u 联通的且未被标记的节点 w1,w2 ……,并入栈,转到2。

        图的广度优先遍历有点像树的层次遍历,是一个分层搜索的过程。假设从  节点开始遍历,首先遍历与  节点联通的点 ,再遍历与  联通的点 ,与  联通的点  。在遍历过程中,要注意不要重复遍历一个节点,往往在遍历过一个节点后就对这个节点做标记。广度优先遍历常常借助队列实现,步骤如下:

  1. 把节点 v 放入队列,标记 v。
  2. 若队列为空则结束,否则取出队列头节点u。
  3. 找出与 u 联通的节点 w1,w2 ......,若未被遍历则遍历,然后标记、入队,转到2。

(2)最小生成树

        对于有 n 个顶点的无向连通图,至少有 n-1 条边,而生成树恰好有 n-1 条边,所以生成树是图的极小连通子图。如果无向连通图是一个网,那么它的所有生成树中必有一棵边的权值总和最小的生成树,称这颗生成树为最小生成树。

        最小生成树是通过贪心算法来构建,通过局部最优来达到整体最优。设  是一个无向联通图,其权值函数为 w。A 是最小生成树的子集,初始为空。通过循环迭代,每次往 A 中加入一条边,且确保加入边后,A 仍是最小生成树的子集,那么加入的这条边就叫做“安全边(safe edge)”。直到把所有的节点都加入到 A 中,循环结束。

        最小生成树可以用Kruskal算法或Prim算法求出。在Kruskal算法中,A 是一个森林,将权值进行排序,选取权值最小的边,若选取的边不形成回路,则为安全边,把它添加到正在生长的森林中。在Prim算法中,A 中的边形成单树,每次循环向 A 中添加一个顶点(权值最小的边连接的顶点)。在算法实现中用到一个最小优先级队列,不在树中的顶点都放在基于权值 key 的最小优先级队列 Q 中,对于顶点 v 来说, key[v] 的值是与树 A 中某一顶点连接的某一条边的最小权值,如果不连接,那么

(3)最短路径

        此问题求从一个源点到其它各点的最短路径。求解单源最短路径的算法主要有Dijkstra算法和Bellman-Ford算法,其中Dijkstra算法用来解决所有边的权为非负的单源最短路径问题,而Bellman-Ford算法可以适用于更一般的问题,图中边的权值可以为负。MADlib的单源最短路径函数就是使用Bellman-Ford算法实现的。如果要得到每一对顶点之间的最短路径,可使用Floyd算法来求解。

二、单源最短路径

(1)问题描述

        给定一个带权有向图  ,其中每条边的权值是一个非负实数。另外,还给定 中的一个顶点,称为源。现在我们要计算从源到所有其它各顶点的最短路径长度。这里的长度是指路上各边权值之和。这个问题通常称为单源最短路径问题。

(2)Dijkstra算法

        Dijkstra算法是一种典型最短路径算法,用于计算一个节点到其它所有节点的最短路径。不过,它针对的是非负权值边。其主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率较低。

        Dijkstra 算法的输入包含了一个有权重的有向图 G,以及 G 中的一个来源顶点 S 。我们以 V 表示 G 中所有顶点的集合,以 E 表示 G 中所有边的集合。 表示从顶点 u 到 v 有路径相连,而边的权重则由权重函数  定义。因此, 就是从顶点 u 到顶点 v 的非负成本值(cost),边的成本可以想像成两个顶点之间的距离。任两点间路径的成本值,就是该路径上所有边的成本值总和。

        已知有 V 中有顶点 s 及 t,Dijkstra 算法可以找到 s 到 t 的最低成本路径(最短路径)。这个算法也可以在一个图中,找到从一个顶点 s 到任何其它顶点的最短路径。

(3)Bellman-Ford算法

        Dijkstra算法无法判断含有负权边图的最短路径。如果遇到负权值,在没有负权回路(回路的权值和为负,即便有负权的边)存在时,可以采用Bellman-Ford算法正确求出最短路径。Bellman-Ford算法能在更普遍的情况下(存在负权边)解决单源点最短路径问题。对于给定的带权(有向或无向)图 , 其源点为 s,加权函数 w 是边集 E 的映射。对图 G 运行Bellman-Ford算法的结果是一个布尔值,表明图中是否存在着一个从源点 s 可达的负权回路。若不存在这样的回路,算法将给出从源点 s 到图 G 的任意顶点 v 的最短路径 d[v]。Bellman-Ford算法寻找单源最短路径的时间复杂度为

        Bellman-Ford算法描述:

  1. 初始化:将除源点外的所有顶点的最短距离估计值 d[v]->+∞,d[s]->0 ;
  2. 迭代求解:反复对边集 E 中的每条边进行松弛操作,使得顶点集 V 中的每个顶点 v 的最短距离估计值逐步逼近其最短距离(运行 |v| - 1 次);
  3. 检验负权回路:判断边集 E 中的每一条边的两个端点是否收敛。如果存在未收敛的顶点,则算法返回false,表明问题无解;否则算法返回true,并且从源点可达的顶点 v 的最短距离保存在 d[v] 中。

三、MADlib的单源最短路径相关函数

1. 单源最短路径函数

(1)语法

graph_sssp( vertex_table,  
            vertex_id,  
            edge_table,  
            edge_args,  
            source_vertex,  
            out_table )

(2)参数

参数名称

数据类型

描述

vertex_table

TEXT

包含图中顶点数据的表名。

vertex_id

TEXT

缺省值为‘id’,vertex_table表中包含顶点的列名。顶点列必须是INTEGER类型,并且数据不能重复,但不要求连续。

edge_table

TEXT

包含边数据的表名。边表必须包含源顶点、目标顶点和边长三列。边表中允许出现回路,并且构成回路的权重可以不同。

edge_args

TEXT

是一个逗号分隔字符串,包含多个“name=value”形式的参数,支持的参数如下:

  • src:INTEGER类型,边表中包含源顶点的列名,缺省值为‘src’。
  • dest:INTEGER类型,边表中包含目标顶点的列名,缺省值为‘dest’。
  • weight:FLOAT8类型,边表中包含边长的列名,缺省值为‘weight’。

source_vertex

INTEGER

算法的起始顶点。此顶点必须在vertex_table表的vertex_id列中存在。

out_table

TEXT

存储单源最短路径的表名,表中的每一行对应一个vertex_table表中的顶点,具有以下列:

  • vertex_id:目标顶点ID,使用vertex_id入参的值作为列名。
  • weight:从源顶点到目标顶点最短路径边长合计,使用weight入参的值作为列名。
  • parent:在最短路径上,本顶点的上一节点,列名为‘parent’。

表1 graph_sssp函数参数说明

2. 路径检索函数

        路径检索函数返回从源顶点到指定目标顶点的最短路径。

(1)语法

graph_sssp( sssp_table,  
            dest_vertex )

(2)参数

  • sssp_table:TEXT类型,单源最短路径函数的输出表名。
  • dest_vertex:INTEGER类型,指定的目标顶点。

四、单源最短路径示例

        单源最短路径问题是图算法的经典问题,在现实中有很多应用,比如在地图中找出两个点之间的最短距离、最小运费等。社交网络中出现的“六度人脉”功能,可以查看到一个用户和一个陌生人之间可以通过哪几个人认识,也就是所谓的六度关系。这个问题也可抽象为一个单源最短路径问题。将用户作为顶点,用户之间的好友关系作为边,“六度关系”就是两个用户之间的最短路径。在这个特殊场景下,所有边的权重都可认为是1。当然,如果用户量巨大,用户好友关系将变得非常复杂,单纯的最短路径算法可能存在性能问题,需要进行改进与优化。

1. 建立表示图的顶点表和边表

drop table if exists vertex, edge;  
create table vertex( id integer );  
create table edge( src integer, dest integer, weight float8 );  

insert into vertex values  
(0), (1), (2), (3), (4), (5), (6), (7);  

insert into edge values  
(0, 1, 1.0), (0, 2, 1.0), (0, 4, 10.0), (1, 2, 2.0),  
(1, 3, 10.0), (2, 3, 1.0), (2, 5, 1.0), (2, 6, 3.0),  
(3, 0, 1.0), (4, 0, -2.0), (5, 6, 1.0), (6, 7, 1.0);

2. 计算从0顶点到各顶点的最短路径

drop table if exists out;  
select madlib.graph_sssp
( 'vertex',      -- 顶点表  
  null,          -- 顶点列名,这里使用缺省值‘id’  
  'edge',        -- 边表  
  null,          -- 边参数,这里全部使用缺省列名  
  0,             -- 计算最短路径的起始顶点  
  'out');        -- 输出表名  
select * from out order by id;

        查询结果如下:

 id | weight | parent   
----+--------+--------  
  0 |      0 |      0  
  1 |      1 |      0  
  2 |      1 |      0  
  3 |      2 |      2  
  4 |     10 |      0  
  5 |      2 |      2  
  6 |      3 |      5  
  7 |      4 |      6  
(8 rows)

3. 获得从0到6的最短路径

dm=# select madlib.graph_sssp_get_path('out',6) as spath; 
   spath   
-----------
 {0,2,5,6}
(1 row)

4. 使用非缺省列名

drop table if exists vertex_alt, edge_alt;  
create table vertex_alt as select id as v_id from vertex;  
create table edge_alt as select src as e_src, dest, weight as e_weight from edge;

5. 计算从1顶点到各顶点的最短路径

drop table if exists out_alt;  
select madlib.graph_sssp
( 'vertex_alt',                  -- 顶点表  
  'v_id',                        -- 顶点列名  
  'edge_alt',                    -- 边表  
  'src=e_src, weight=e_weight',  -- 边参数,指定顶点和边长的列名  
  1,                             -- 计算最短路径的起始顶点  
  'out_alt');                    -- 输出表名  
select * from out_alt order by v_id;

        结果:

 v_id | e_weight | parent   
------+----------+--------  
    0 |        4 |      3  
    1 |        0 |      1  
    2 |        2 |      1  
    3 |        3 |      2  
    4 |       14 |      0  
    5 |        3 |      2  
    6 |        4 |      5  
    7 |        5 |      6  
(8 rows)  

五、小节

        图算法是一类特殊的数据挖掘方法,常被用于解决确定图连通性、寻找最短路径等相关问题。实际应用中,图算法广泛用于社交网络分析(如Community Detection)、互联网(如PageRank)、计算生物学(如研究分子活动路径)、电子工程(如集成电路设计)、科学计算(如图划分)、安全领域(如安全事件分析)等很多方面。图算法主要包括图遍历、图匹配、最小生成树、最短路径等几大类,每一类中有多种算法。MADlib仅提供了一种图算法模型,即单源最短路径模型,它是使用Bellman-Ford算法实现的。

文章最后发布于: 2018-03-15 11:01:28
展开阅读全文
0 个人打赏
私信求帮助

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览