加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

scala – Tarjan的强关联组件算法的功能实现

发布时间:2020-12-16 09:36:54 所属栏目:安全 来源:网络整理
导读:我在Scala中前进了 implemented textbook version of Tarjan’s SCC algorithm。然而,我不喜欢代码 – 这是非常必要的/程序性的许多变异状态和簿记指数。是否有更多的“功能”版本的算法?我认为,与功能版本不同,算法的命令式版本隐藏了算法背后的核心思
我在Scala中前进了 implemented textbook version of Tarjan’s SCC algorithm。然而,我不喜欢代码 – 这是非常必要的/程序性的许多变异状态和簿记指数。是否有更多的“功能”版本的算法?我认为,与功能版本不同,算法的命令式版本隐藏了算法背后的核心思想。我发现 someone else encountering the same problem有这个特殊的算法,但是我没有能够把他的Clojure代码翻译成一般的Scala。

注意:如果有人想尝试,我有一个很好的设置生成随机图和tests your SCC algorithm vs running Floyd-Warshall

解决方法

以下功能Scala代码生成将代表分配给图形的每个节点的地图。每个代表识别一个强连接的组件。该代码基于Tarjan针对强连接组件的算法。

为了理解算法,可以了解dfs函数的折叠和合约。

def scc[T](graph:Map[T,Set[T]]): Map[T,T] = {
  //`dfs` finds all strongly connected components below `node`
  //`path` holds the the depth for all nodes above the current one
  //'sccs' holds the representatives found so far; the accumulator
  def dfs(node: T,path: Map[T,Int],sccs: Map[T,T]): Map[T,T] = {
    //returns the earliest encountered node of both arguments
    //for the case both aren't on the path,`old` is returned
    def shallowerNode(old: T,candidate: T): T = 
      (path.get(old),path.get(candidate)) match {
        case (_,None) => old
        case (None,_) => candidate
        case (Some(dOld),Some(dCand)) =>  if(dCand < dOld) candidate else old
      }

    //handle the child nodes
    val children: Set[T] = graph(node)
    //the initially known shallowest back-link is `node` itself
    val (newState,shallowestBackNode) = children.foldLeft((sccs,node)){
      case ((foldedSCCs,shallowest),child) =>
        if(path.contains(child))
          (foldedSCCs,shallowerNode(shallowest,child))
        else {
          val sccWithChildData = dfs(child,path + (node -> path.size),foldedSCCs)
          val shallowestForChild = sccWithChildData(child)
          (sccWithChildData,shallowestForChild))
        }
    }

    newState + (node -> shallowestBackNode)
  }

  //run the above function,so every node gets visited
  graph.keys.foldLeft(Map[T,T]()){ case (sccs,nextNode) =>
    if(sccs.contains(nextNode))
      sccs
    else
      dfs(nextNode,Map(),sccs)
  }
}

我已经在Wikipedia页面上的示例图上测试了代码。

与命令版本的区别

与原始实现相反,我的版本避免显式解开堆栈,只需使用正确的(非尾部)递归函数。堆栈由称为路径的持久映射表示。在我的第一个版本中,我使用了一个List作为堆栈;但是由于不得不搜索包含元素,因此效率较低。

效率

代码效率相当高。对于每个边缘,您必须更新和/或访问不可变地图路径,其中O(log | N |)的总和为O(| E | log | N |)。这与命令式版本实现的O(| E |)相反。

线性时间实现

Chris Okasaki的回答中的论文在Haskell中提供了一个线性时间解决方案来寻找强连接的组件。他们的实现是基于Kosaraju的算法,用于查找SCC,它基本上需要两次深度优先遍历。本文的主要贡献似乎是Haskell中的一个懒惰的线性时间DFS实现。

实现线性时间解决方案所需要的是具有O(1)单例添加和成员资格测试的集合。这基本上是同样的问题,使得该答案中给出的解决方案具有比命令式解决方案更高的复杂性。他们使用Haskell中的状态线程来解决它,这也可以在Scala中完成(请参阅Scalaz)。因此,如果愿意使代码变得复杂,可以将Tarjan的SCC算法实现为功能性O(| E |)版本。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读