Graphx的數三角形算法TriangleCount用于統計每個頂點所在的三角形個數。
1.1 簡介
對網絡圖中進行三角形個數計數可以根據三角形數量反應網絡中的稠密程度和質量。
1.2 應用場景
(一)用于社區發現
如微博中你關注的人也關注你,大家的關注關系中有很多三角形,說明社區很強很穩定,大家聯系比較緊密;如果一個人只關注了很多人,卻沒有形成三角形,則說明社交群體很小很松散。
(二)衡量社群耦合關系的緊密程度
通過三角形數量來反應社區內部的緊密程度,作為一項參考指標。
1.3 算法思路
計算規則:
如果一條邊的兩個頂點有共同的鄰居,則這三個點構成三角形。
計算步驟:
1. 為每個節點計算鄰居集合
2. 對于每條邊,計算兩端節點鄰居集合的交集,將交集中元素個數告知兩端節點,
該個數即對應著節點關聯的三角形數。
3. 對每個節點合并三角形數目統計總數,由于三角形中一個頂點關聯兩條邊,所以
對于同一個三角形而言,一個頂點計算了兩次,故最終結果需要除以2。
1.4 源碼解析
object TriangleCount {
def run[VD: ClassTag, ED: ClassTag](graph: Graph[VD, ED]): Graph[Int, ED] = {
// Transform the edge data something cheap to shuffle and then canonicalize
//得到的是一個無自連邊且無重復邊的、邊是從小id指向大id的圖
val canonicalGraph = graph.mapEdges(e => true).removeSelfEdges().convertToCanonicalEdges()
// Get the triangle counts
val counters = runPreCanonicalized(canonicalGraph).vertices
// Join them bath with the original graph
graph.outerJoinVertices(counters) { (vid, _, optCounter: Option[Int]) =>
optCounter.getOrElse(0)
}
}
def runPreCanonicalized[VD: ClassTag, ED: ClassTag](graph: Graph[VD, ED]): Graph[Int, ED] = {
// 構建鄰居集合
val nbrSets: VertexRDD[VertexSet] =
// 收集鄰居節點,邊方向為Either,保證點的入邊和出邊連接的鄰居點都會被收集
graph.collectNeighborIds(EdgeDirection.Either).mapValues { (vid, nbrs) =>
val set = new VertexSet(nbrs.length)
var i = 0
while (i < nbrs.length) {
// prevent self cycle
if (nbrs(i) != vid) {
set.add(nbrs(i))
}
i += 1
}
set
}
// 更新圖中頂點的屬性為鄰居點集合
val setGraph: Graph[VertexSet, ED] = graph.outerJoinVertices(nbrSets) {
(vid, _, optSet) => optSet.getOrElse(null)
}
def edgeFunc(ctx: EdgeContext[VertexSet, ED, Int]) {
// 在邊上操作源點和終點的鄰居集合是,遍歷較小的集合,加快遍歷速度
val (smallSet, largeSet) = if (ctx.srcAttr.size < ctx.dstAttr.size) {
(ctx.srcAttr, ctx.dstAttr)
} else {
(ctx.dstAttr, ctx.srcAttr)
}
val iter = smallSet.iterator
var counter: Int = 0
while (iter.hasNext) {
val vid = iter.next()
if (vid != ctx.srcId && vid != ctx.dstId && largeSet.contains(vid)) {
counter += 1
}
}
ctx.sendToSrc(counter)
ctx.sendToDst(counter)
}
// 沿著圖中的邊計算兩個頂點的鄰居集合的交集,并為每個頂點合并消息(消息為三角形個數)
val counters: VertexRDD[Int] = setGraph.aggregateMessages(edgeFunc, _ + _)
graph.outerJoinVertices(counters) { (_, _, optCounter: Option[Int]) =>
val dblCount = optCounter.getOrElse(0)
// 算法為每個三角形計算了兩次,所以結果是偶數
require(dblCount % 2 == 0, "Triangle count resulted in an invalid number of triangles.")
dblCount / 2 //注意最后需要除以2,每個三角形被計算了兩遍
}
}
}