/**連通體*/
object ConnectComponents {
/**在圖中若從某個頂點Vp出發,沿着一些邊經過頂點V1,V2,。。。Vm到達Vg則稱頂點序列(Vp,V1,V2....Vm,Vg)爲從Vp到Vg的路徑,其中Vp是路徑的起始點,Vg爲路徑終點。
* 路徑上的邊的數目成爲該路徑的長度
* 連通:在無向圖中,若從頂點Vi到頂點Vj之間有路徑稱爲這兩個頂點是連通的。
* 連通圖:若圖中任意一對頂點之間都是連通的,則稱爲此圖爲連通圖。
* 連通分量:非連通圖中的每個連通部分稱爲連通分量
* 強連通:對於有向圖,若從頂點Vi到頂點Vj到頂點Vi之間都有路徑,則稱這兩頂點是強連通的。
* 強連通圖:若有向圖中任何一對頂點都是強連通的,則此圖爲強連通圖。
*
* 性質:
* 無向連通圖,則邊E的數目大於等於頂點V的數目減1:|E| >= |V| - 1,反之不成立;
* 有向強連通圖的必要條件是邊的數目大於等於頂點的數目:|E| >= |V|,反之不成立。
* 沒有迴路的無向圖是連通的當且它是樹,即等價於:|E| = |V| - 1
* */
def conCompts(sc: SparkContext): Unit ={
val graph = Graph(sc.makeRDD((1L to 7L).map((_,""))),
sc.makeRDD(Array(Edge(2L,5L,"配偶"),Edge(5L,3L,"親戚"),Edge(3L,2L,"子女"),Edge(4L,5L,"父母"),Edge(6L,7L,"親戚")))
)
graph
.connectedComponents()
.vertices
.map(_.swap)
.groupByKey().map(_._2)
.foreach(println)
}
/**強連通:
* (1)對圖中所有節點設定初始連通分支id,用自己的節點id作爲所屬連通分支的id,並將所有節點打上初始標記false;
* (2)首先做循環,將只有出邊或入邊的節點標記爲true,將 **只存在單向邊的或者孤立的節點** 和 **已經
* 確認且打好標記的強連通分量中的節點**(即被標記爲true的節點)從圖中去除;
* (3)爲圖中節點正向着色,先用節點id爲自身着色,之後沿着出邊向鄰居節點發送自己的着色id(只有較小的着色id向較大的着色id的節點發送消息)。
* (4)爲着色完成的圖中節點反向打標籤(是否完成連通分支id標記)。在着色完成的圖中,節點id與節點所在連通分支id相同時表明
* 該節點是着色的root節點,標記爲true。若一個節點對應的入邊的另外一個節點是true,則該節點也被標記爲true。
* 節點沿着入邊由src節點向dst節點發送自身標記情況,只有收到true的消息則節點便標記爲true。(只有着色相同,
* 且一條邊上dst節點——發消息者是true但是src節點——收消息者是false時,dst節點纔會向src節點發送消息)*/
def strongConCompt(sc: SparkContext): Unit ={
val graph = Graph(sc.makeRDD((1L to 7L).map((_,""))),
sc.makeRDD(Array(Edge(2L,5L,"配偶"),Edge(5L,3L,"親戚"),Edge(3L,2L,"子女"),Edge(4L,5L,"父母"),Edge(6L,7L,"親戚")))
)
val strongConCpt: Graph[VertexId, String] = graph.stronglyConnectedComponents(3)
strongConCpt.vertices.foreach(println)
}
/**StronglyConnectedComponents源碼*/
def strongCompt[VD: ClassTag, ED: ClassTag](graph: Graph[VD, ED], numIter: Int): Graph[VertexId, ED] = {
// 初始化圖,將節點id作爲節點屬性,sccGraph是最後的返回結果圖
var sccGraph = graph.mapVertices { case (vid, _) => vid }
// 在迭代中使用的圖
var sccWorkGraph = graph.mapVertices { case (vid, _) => (vid, false) }.cache()
// 輔助變量prevSccGraph,用來unpersist緩存圖
var prevSccGraph = sccGraph
var numVertices = sccWorkGraph.numVertices
var iter = 0
while (sccWorkGraph.numVertices > 0 && iter < numIter) {
iter += 1
// 此處循環內部工作:
// 1.第一次循環進入時:將sccWorkGrpah圖中只有單向邊的節點或者孤立節點去掉; 後面循環進入時:將sccWorkGraph圖中已經標識完成的強連通分量去掉。
// 2.更新圖中節點所屬的強連通分支id
// 只有在第一次進入第一層循環時,第一層循環內部的do-while循環纔會循環多次,第2次以上只會只運行一次do{}的內容,因爲後面圖中不存在單向節點了。
do {
numVertices = sccWorkGraph.numVertices
sccWorkGraph = sccWorkGraph.outerJoinVertices(sccWorkGraph.outDegrees) {
(vid, data, degreeOpt) => if (degreeOpt.isDefined) data else (vid, true)
}.outerJoinVertices(sccWorkGraph.inDegrees) {
(vid, data, degreeOpt) => if (degreeOpt.isDefined) data else (vid, true)
}.cache() //得到圖中的有雙向邊的節點(vid,false), 單向邊或者孤立節點(vid,true),並且已經成功標記完連通分支的節點自身屬性便是(vid,true)
// 拿到圖中只有單向邊的節點或孤立節點
val finalVertices = sccWorkGraph.vertices
.filter { case (vid, (scc, isFinal)) => isFinal}
.mapValues { (vid, data) => data._1}
// //外部第一次循環不會變動sccGraph節點的屬性,只有在第二次開始纔會將頂點所屬的強連通分支id更新到圖節點屬性中。
sccGraph = sccGraph.outerJoinVertices(finalVertices) {
(vid, scc, opt) => opt.getOrElse(scc)
}.cache()
sccGraph.vertices.count()
sccGraph.edges.count()
prevSccGraph.unpersist(blocking = false)
prevSccGraph = sccGraph
//只保留屬性attr._2爲false的節點(這些節點是未完成連通分量打標籤的節點,後面進入pregel重新着色)
sccWorkGraph = sccWorkGraph.subgraph(vpred = (vid, data) => !data._2).cache()
} while (sccWorkGraph.numVertices < numVertices)
// 如果達到迭代次數則返回此時的sccGraph,將不再進入pregel進行下一步的着色和打標籤。
if (iter < numIter) {
// 初始用vid爲自身節點着色,每次重新進入pregel的圖將重新着色
sccWorkGraph = sccWorkGraph.mapVertices { case (vid, (color, isFinal)) => (vid, isFinal) }
sccWorkGraph = Pregel[(VertexId, Boolean), ED, VertexId](
sccWorkGraph, Long.MaxValue, activeDirection = EdgeDirection.Out)(
// vprog: 節點在自己所屬連通分支和鄰居所屬分支中取最小者更新自己。
(vid, myScc, neighborScc) => (math.min(myScc._1, neighborScc), myScc._2),
// sendMsg:正向(out)向鄰居傳播自身所屬的連通分支(只有當自己所屬連通分支比鄰居小纔會發送消息)
e => {
if (e.srcAttr._1 < e.dstAttr._1) {
Iterator((e.dstId, e.srcAttr._1))
} else {
Iterator()
}
},
// mergeMsg: 多條消息(鄰居的連通分支)取最小者
(vid1, vid2) => math.min(vid1, vid2))
//第二個pregel:爲着色後的節點打標籤,final表示該節點的連通分支id已經標記完成。
sccWorkGraph = Pregel[(VertexId, Boolean), ED, Boolean](
sccWorkGraph, false, activeDirection = EdgeDirection.In)(
// vprog: 如果節點id和所屬連通分支id相同,則該節點是root
// root節點是完成連通分支標記的節點,是final (final是被標記爲true)
// 如果節點和final節點是鄰居(收到的消息是final),則該節點也是final
(vid, myScc, existsSameColorFinalNeighbor) => {
val isColorRoot = vid == myScc._1
(myScc._1, myScc._2 || isColorRoot || existsSameColorFinalNeighbor)
},
// 從完成着色的分量的root開始,反向(in)遍歷節點,當一條邊上兩個節點的着色不同時則不發送消息。
e => {
val sameColor = e.dstAttr._1 == e.srcAttr._1
val onlyDstIsFinal = e.dstAttr._2 && !e.srcAttr._2
if (sameColor && onlyDstIsFinal) {
Iterator((e.srcId, e.dstAttr._2))
} else {
Iterator()
}
},
(final1, final2) => final1 || final2)
}
}
sccGraph
}
def createGraph[VD: ClassTag, ED: ClassTag](sc: SparkContext): Graph[String, String] ={
val graph = Graph(sc.makeRDD((1L to 7L).map((_,""))),
sc.makeRDD(Array(Edge(2L,5L,"配偶"),Edge(5L,3L,"親戚"),Edge(3L,2L,"子女"),Edge(4L,5L,"父母"),Edge(6L,7L,"親戚"))))
graph
}
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setAppName("graphx visual").setMaster("local[2]")
val sc = SparkContext.getOrCreate(sparkConf)
conCompts(sc)
val graph = createGraph(sc)
strongCompt(graph, 3)
}
}