有向无环图DAG

拓扑排序的实现原理

实现拓扑排序一般有两种思路,一种基于贪心,一种基于深度优先搜索。接下来分别介绍这两种思路:

1、贪心

1、从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。
2、从图中删除该顶点和所有以它为起点的有向边。
3、重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。

2、DFS

 

有向无环图工具类的scala实现,包括拓扑排序(根据入度实现)

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer


class DAG[K, V](_nodes: Map[K, V], _edges: Traversable[(K, K)]) {

    protected case class Edge(src: K, dst: K)

    val nodes = mutable.HashMap[K, V]() ++ _nodes
    val edges: Set[Edge] = _edges.map(e => Edge(e._1, e._2)).toSet

    private lazy val allNodeKeys = ((nodes.keySet) ++ edges.flatMap(e => Array(e.src, e.dst))).toSet
    private lazy val upstreamKeys: Map[K, Set[K]] = edges.groupBy(_.dst).map { case (k, e) => (k, e.map(_.src)) }
    private lazy val downstreamKeys: Map[K, Set[K]] = edges.groupBy(_.src).map { case (k, e) => (k, e.map(_.dst)) }

    require(!hasCycle, "has cycle")

    lazy val topoSortedKeys: Array[K] = {
        val inDegree = new mutable.HashMap[K, Int]()
        inDegree ++= allNodeKeys.map(k=>(k,0))
        inDegree ++= upstreamKeys.map(x=>(x._1, x._2.size))

        val queue = mutable.Queue[K]() ++= zeroIndegreeKeys
        val res = ArrayBuffer.empty[K]
        while(queue.nonEmpty){
            val key = queue.dequeue()
            res.append(key)
            downstreamKeys.get(key).map { downstreams =>
                downstreams.map{ dst =>
                    inDegree(dst) -= 1
                    inDegree(dst) match {
                        case 0 => queue.enqueue(dst)
                        case d if d <0 =>  throw new IllegalStateException("indegree < 0")
                        case _ =>
                    }
                }
            }
        }
        res.toArray
    }


    private def hasCycle: Boolean = {
        // 存在环的情况下拓扑排序的结果不能遍历所有节点
        allNodeKeys.size != topoSortedKeys.length
    }

    /**
      * 输出拓扑排序结果
      *
      */
    def topoSorted: Array[(K, Option[V])] = {
        topoSortedKeys.map(key => (key, nodes.get(key)))
    }

    /** *
      * 0入度节点
      */
    def zeroIndegreeKeys: Array[K] = {
        //upstreamKeys只包含有上游的节点,排除0入度节点
        (allNodeKeys -- upstreamKeys.keys).toArray
    }

    /** *
      * 获取上游节点
      */
    def upstream(key: K): Array[(K, Option[V])] = {
        upstreamKeys.getOrElse(key, Set.empty[K]).map(k => (k, nodes.get(k))).toArray
    }

    /** *
      * 获取下游节点
      */
    def downstream(key: K): Array[(K, Option[V])] = {
        downstreamKeys.getOrElse(key, Set.empty[K]).map(k => (k, nodes.get(k))).toArray
    }

    /** *
      * 更新节点状态
      */
    def updateNode(key: K, value: V): Unit = {
        nodes.get(key) match {
            case None => throw new NoSuchElementException(s"Not Fount $key")
            case _ => nodes(key) = value
        }
    }
}

object DAG {
    def apply[K, V](nodes: Map[K, V], edges: Traversable[(K, K)]): DAG[K, V] = new DAG(nodes,edges)
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值