Graphx图算法【1】三角形TriangleCount

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,每个三角形被计算了两遍
    }
  }
}

推荐阅读更多精彩内容