Spark的最短路径详解

原创 2016年08月29日 12:51:35


import org.apache.spark.graphx._  
import org.apache.spark.SparkContext  
import org.apache.log4j.{Level, Logger}
import org.apache.spark.{SparkContext, SparkConf}
import org.apache.spark.rdd.RDD
// Import random graph generation library  
//import org.apache.spark.graphx.util.GraphGenerators  
import org.apache.spark.graphx.lib.ShortestPaths
  
 
object Pregel {  
    def main(args: Array[String]) {  
 //屏蔽日志
    Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
    Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)

        //设置运行环境
        val conf = new SparkConf().setAppName("SimpleGraphX") 
        val sc = new SparkContext(conf) 
        
//        val edgeFile:RDD[String] = sc.textFile("xrli/PregelEdge.txt")
//        val vertexFile:RDD[String] = sc.textFile("xrli/PregelVertex.txt")
//        
//        //edge
//        val edge = edgeFile.map { e =>
//        val fields = e.split(" ")
//        Edge(fields(0).toLong,fields(1).toLong,fields(2))
//        }
//       //vertex
//        val vertex = vertexFile.map{e=>
//        val fields = e.split(" ")
//        (fields(0).toLong,fields(1))
//
//        }
//
//        val graph = Graph(vertex,edge,"").persist()
//        println(graph.edges.collect.mkString("\n")) 
        
      //设置顶点和边,注意顶点和边都是用元组定义的Array
    //顶点的数据类型是VD:(String,Int)
    val vertexArray = Array(
      (1L, ("Alice", 28)),              //在这里后面的属性("Alice", 28)没有用上。
      (2L, ("Bob", 27)),
      (3L, ("Charlie", 65)),
      (4L, ("David", 42)),
      (5L, ("Ed", 55)),
      (6L, ("Fran", 50))
    )
    //边的数据类型ED:Int
    val edgeArray = Array(
      Edge(2L, 1L, 7),
      Edge(2L, 4L, 2),
      Edge(3L, 2L, 4),
      Edge(3L, 6L, 3),
      Edge(4L, 1L, 1),
      Edge(5L, 2L, 2),
      Edge(5L, 3L, 8),
      Edge(5L, 6L, 3)
    )
 
    //构造vertexRDD和edgeRDD
    val vertexRDD: RDD[(Long, (String, Int))] = sc.parallelize(vertexArray)
    val edgeRDD: RDD[Edge[Int]] = sc.parallelize(edgeArray)
 
    //构造图Graph[VD,ED]
    val graph: Graph[(String, Int), Int] = Graph(vertexRDD, edgeRDD)
 
    
    val sourceId: VertexId = 5L // 定义源点
    
    //初始化一个新的图,该图的节点属性为graph中各节点到原点的距离  
    val initialGraph = graph.mapVertices((id, _) => if (id == sourceId) 0.0 else Double.PositiveInfinity)
    println(initialGraph.vertices.collect.mkString("\n")) 
    println(initialGraph.edges.collect.mkString("\n"))

    val sssp = initialGraph.pregel(Double.PositiveInfinity)(
      // Vertex Program,节点处理消息的函数,dist为原节点属性(Double),newDist为消息类(Double) 
      (id, dist, newDist) => math.min(dist, newDist),

    // Send Message,发送消息函数,返回结果为(目标节点id,消息(即最短距离))  
      triplet => {  // 计算权重
        if (triplet.srcAttr + triplet.attr < triplet.dstAttr) {
          Iterator((triplet.dstId, triplet.srcAttr + triplet.attr))
        } else {
          Iterator.empty
        }
      },

//Merge Message,对消息进行合并的操作,类似于Hadoop中的combiner 
      (a,b) => math.min(a,b) // 最短距离
    )
    println(sssp.vertices.collect.mkString("\n"))
 
    sc.stop()
  }
}

该程序是求所有节点到5号点的最短距离长度。

Spark的最短路径详解


建立好initialGraph后    
   println(initialGraph.vertices.collect.mkString("\n")) 打印结果如下
(1,Infinity)                                                                    
(2,Infinity)
(3,Infinity)
(4,Infinity)
(5,0.0)
(6,Infinity)
println(initialGraph.edges.collect.mkString("\n")) 打印结果如下
Edge(2,1,7)                                                                     
Edge(2,4,2)
Edge(3,2,4)
Edge(3,6,3)
Edge(4,1,1)
Edge(5,2,2)
Edge(5,3,8)
Edge(5,6,3)

结果输出:
(1, 5.0)
(2, 2.0)
(3, 8.0)
(4, 4.0)
(5, 0.0)
(6, 3.0)

Pregel的计算模型


某个图节点v从之前的超级步中接收到的消息队列中查找目前看到的最短路径,如果这个值比节点v当前获得的最短路径小,说明找到更短的路径,则更新节点数值为新的最短路径,之后将新值通过邻接节点传播出去,否则将当前节点转换为不活跃状态。


Pregel操作是一个约束到图拓扑的批量同步(bulk-synchronous)并行消息抽象。Pregel操作者执行一系列的超级步骤(super steps),在这些步骤中,顶点从 之前的超级步骤中接收进入(inbound)消息的总和,为顶点属性计算一个新的值,然后在以后的超级步骤中发送消息到邻居顶点。不像Pregel而更像GraphLab,消息作为一个边三元组的函数被并行 计算,消息计算既访问了源顶点特征也访问了目的顶点特征。在超级步中,没有收到消息的顶点被跳过。当没有消息遗留时,Pregel操作停止迭代并返回最终的图。
注意,与更标准的Pregel实现不同的是,GraphX中的顶点仅仅能发送信息给邻居顶点,并利用用户自定义的消息函数构造消息。这些限制允许在GraphX进行额外的优化。

主要分为三个函数:
1、vertexProgram函数

2、sendMessage函数

3、messageCombiner函数


def pregel[A](initialMsg: A, maxIterations: Int, activeDirection: EdgeDirection)(

vprog: (VertexId, Double, A) => Double, 

sendMsg: EdgeTriplet[Double, Int] => Iterator[(VertexId, A)], 

mergeMsg: (A, A) => A)

: Graph[Double, Int]

pregel有两个参数列表。第一个参数列表包含配置参数初始消息、最大迭代数、发送消息的边的方向(默认是沿边方向出)。第二个参数列表包含用户自定义的函数用来接收消息(vprog)、计算消息(sendMsg)、合并消息(mergeMsg)。


这里的泛型A在本程序中是Double类型

首先initialGraph.pregel(Double.PositiveInfinity)

这里传递了一个参数:initialMsg:在第一次迭代的时候顶点收到的消息,为无穷大Double.PositiveInfinity。这里也可以写成initialGraph.pregel[Double](Double.PositiveInfinity)


然后vprog:用户定义的顶点程序运行在每一个顶点中,负责接收进来的信息,和计算新的顶点值。在第一次迭代的时候,所有的顶点程序将会被默认的initialMsg调用,在次轮迭代中,顶点程序只有接收到message才会被调用。

 (id, dist, newDist) => math.min(dist, newDist),

显而易见的是:两个消息来的时候,取它们当中路径的最小值。



initialGraph(vertexRDD, edgeRDD,属性)

这个属性是每个顶点到原点5的距离。



SendMessage函数的原理是:

 triplet => {  // 计算权重
        if (triplet.srcAttr + triplet.attr < triplet.dstAttr) {
          Iterator((triplet.dstId, triplet.srcAttr + triplet.attr))
        } else {
          Iterator.empty
        }
      },


我们再回顾一下triplet的形式

Spark的最短路径详解
这里vertices形式如下(2,Infinity),(1,Infinity) 

Edges形式如下:   Edge(2L, 1L, 7),

假设我们当前处理的triplet的 A对应点2,B对应点1

所以triplet.srcAttr 就是点2到原点5的距离,

 triplet.attr 就是这里的7(A、B间的长度权重)

triplet.dstAttr就是点1到原点5的距离。



如果triplet.srcAttr + triplet.attr < triplet.dstAttr)

即如果和更小,则通过Iterator发送该信息到目标顶点的函数。

Iterator((triplet.dstId, triplet.srcAttr + triplet.attr))

该信息的结构为(目标节点id,消息(即最短距离)),否则不发送。


注意到Pregel图算法整体思路是通过迭代计算每个顶点的属性直到到达定点条件。

第一次迭代,初始消息发的是无穷大,这个反正最后都会被math.min(a,b)给弄掉的。

下一次迭代的时候,对于下面几条边对应的triple暂时都不会有新的消息出来,还都是无穷大

Edge(2,1,7)                                                                     
Edge(2,4,2)
Edge(3,2,4)
Edge(3,6,3)
Edge(4,1,1)
而对于下面这几条边对应的triple则会有消息更新
Edge(5,2,2)
Edge(5,3,8)
Edge(5,6,3)

比如

Edge(5,2,2)

Spark的最短路径详解
可以看出triplet.srcAttr + triplet.attr < triplet.dstAttr)   这里是 0+2

Iterator((triplet.dstId, triplet.srcAttr + triplet.attr))  为(2, 2)

这样,再到下一轮的迭代中,节点处理函数vprog:  (id, dist, newDist) => math.min(dist, newDist),

dist为原节点属性(Double)点2原来的节点属性是Inf,newDist为消息2。

所以,就会更新新的节点2属性值为2。

。。。。以此类推。


//Merge Message,对消息进行合并的操作,类似于Hadoop中的combiner 
      (a,b) => math.min(a,b) 
// 对于同一定点所有接受到的消息(这里是最短距离)进行整合,取最小的。因为每一轮迭代中跟某一节点有关的消息可能不止一个,而我们只关心最小的就行了。

相关文章推荐

Spark组件之GraphX学习16--最短路径ShortestPaths

更多代码请见:https://github.com/xubo245/SparkLearning 1解释 求图中的最短路径,更多的请见参考【3】,这篇写的很详细 2.代码: /** * @a...

SparkGraphX加权最短路径算法实现

SparkGraphX加权最短路径算法实现 标签: sparkgraphxdijkstra 2016-10-14 13:57 477人阅读 评论(0) 收藏 举报  分类: ...

Spark组件之GraphX学习9--使用pregel函数求单源最短路径

更多代码请见:https://github.com/xubo245/SparkLearning 1解释 使用pregel函数求单源最短路径 GraphX中的单源点最短路径例子...

Spark的Graphx学习笔记--Pregel

hi

《图论》——最短路径 Dijkstra算法(戴克斯特拉算法)

十大算法之Dijkstra算法: 最短路径是图论算法中的经典问题。图分为有向图、无向图,路径权值有正值、负值,针对不同的情况需要分别选用不同的算法。在维基上面给出了各种不同的场景应用不同的算法的基本...

SparkGraphX加权最短路径算法实现

版本:spark 1.6 该版本自带的最短路径算法shortestPaths没办法自定义权重(默认每条边的权重都一样),不符合现实生活,比如在地图中计算两个位置的最短路线,要考虑线路的长度,线路的拥...

两点之间最短路径算法(Single-Dijkstra-shortest path)

摘要 本文主要讲述最短路径算法,一个主要原因是网上的“基于Matlab实现的两点之间最短路径算法”存在各种实现错误,目前为止还没有找到一个完全正确的。所以,本人改正相关错误,上传个正确的版本,即:采用...

GraphX中Pregel单源点最短路径

GraphX中的单源点最短路径例子,使用的是类Pregel的方式。 核心部分是三个函数: 1.节点处理消息的函数  vprog: (VertexId, VD, A) => VD (节点id,节点属...

spark graphX求最短路径以及中间结点

老外论坛找了一个最短路径的代码,并保存中间节点,分享一下 import org.apache.spark.{SparkConf, SparkContext} import org.apache....

Graphx 最短路径源码解析

最短路径测试代码下面主要是对Spark图计算框架GraphX中的单源点最短路径的源码进行解析。 GraphX最短路径求解中使用了Pregel模型,这是一个非常高效的图计算模型。但目前最短路径有如下限制...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Spark的最短路径详解
举报原因:
原因补充:

(最多只允许输入30个字)