sca*_*1sk 13 functional-programming scala graph-theory partial-ordering directed-acyclic-graphs
这是我的问题:我有一个序列S(非空但可能不是不同)设置s_i,并且对于每个s_i需要知道S(i≠j)中有多少个集合s_j是s_i的子集.
我还需要增量性能:一旦我完成所有计数,我可以用s_i的某个子集替换一组s_i并逐步更新计数.
使用纯功能代码执行所有这些将是一个巨大的优势(我在Scala中的代码).
由于set包含是一个部分排序,我认为解决我的问题的最好方法是构建一个DAG,它表示集合的Hasse图,边表示包含,并将一个整数值连接到表示大小的每个节点节点下方的子d加1.但是,我已经被困了好几天试图开发从部分排序构建Hasse图的算法(让我们不谈增量!),即使我认为它会是一些标准本科教材.
这是我的数据结构:
case class HNode[A] (
val v: A,
val child: List[HNode[A]]) {
val rank = 1 + child.map(_.rank).sum
}
Run Code Online (Sandbox Code Playgroud)
我的DAG由根列表和一些部分排序定义:
class Hasse[A](val po: PartialOrdering[A], val roots: List[HNode[A]]) {
def +(v: A): Hasse[A] = new Hasse[A](po, add(v, roots))
private def collect(v: A, roots: List[HNode[A]], collected: List[HNode[A]]): List[HNode[A]] =
if (roots == Nil) collected
else {
val (subsets, remaining) = roots.partition(r => po.lteq(r.v, v))
collect(v, remaining.map(_.child).flatten, subsets.filter(r => !collected.exists(c => po.lteq(r.v, c.v))) ::: collected)
}
}
Run Code Online (Sandbox Code Playgroud)
我很困在这里.我最后向DAG添加了一个新值v:
collect函数) ,可能是非最佳的甚至是有缺陷的).我还没有完全实现这个算法,但是对于我看似简单的问题,它看起来似乎是不完整的,并且不是最优的.是否有一些更简单的算法可用(谷歌对此无能为力)?
经过一番工作,我终于按照我最初的直觉解决了我的问题。收集方法和排名评估有缺陷,我用尾递归重写了它们作为奖励。这是我获得的代码:
\n\nfinal case class HNode[A](\n val v: A,\n val child: List[HNode[A]]) {\n val rank: Int = 1 + count(child, Set.empty)\n\n @tailrec\n private def count(stack: List[HNode[A]], c: Set[HNode[A]]): Int =\n if (stack == Nil) c.size\n else {\n val head :: rem = stack\n if (c(head)) count(rem, c)\n else count(head.child ::: rem, c + head)\n }\n}\n\n// ...\n\n private def add(v: A, roots: List[HNode[A]]): List[HNode[A]] = {\n val newNode = HNode(v, collect(v, roots, Nil))\n attach(newNode, roots)\n }\n\n private def attach(n: HNode[A], roots: List[HNode[A]]): List[HNode[A]] =\n if (roots.contains(n)) roots\n else {\n val (supersets, remaining) = roots.partition { r =>\n // Strict superset to avoid creating cycles in case of equal elements\n po.tryCompare(n.v, r.v) == Some(-1)\n }\n if (supersets.isEmpty) n :: remaining.filter(r => !po.lteq(r.v, n.v))\n else {\n supersets.map(s => HNode(s.v, attach(n, s.child))) ::: remaining\n }\n }\n\n @tailrec\n private def collect(v: A, stack: List[HNode[A]], collected: List[HNode[A]]): List[HNode[A]] =\n if (stack == Nil) collected\n else {\n val head :: tail = stack\n\n if (collected.exists(c => po.lteq(head.v, c.v))) collect(v, tail, collected)\n else if (po.lteq(head.v, v)) collect(v, tail, head :: (collected.filter(c => !po.lteq(c.v, head.v))))\n else collect(v, head.child ::: tail, collected)\n }\nRun Code Online (Sandbox Code Playgroud)\n\n我现在必须检查一些优化:\n - 在收集子集时切断具有完全不同集的分支(如 Rex Kerr 建议的那样)\n - 看看按大小对集进行排序是否可以改进过程(如 mitchus 建议的那样)
\n\n以下问题是解决 add() 操作的(最坏情况)复杂性。\n如果集合数为 n,最大集合的大小为 d,则复杂性可能为 O(n\xc2\xb2d) ,但希望可以完善一下。我的推理是这样的:如果所有集合都是不同的,那么 DAG 将被简化为根/叶的序列。因此,每次我尝试将节点添加到数据结构时,我仍然必须检查是否包含已存在的每个节点(在收集和附加过程中)。这导致 1 + 2 + \xe2\x80\xa6 + n = n(n+1)/2 \xe2\x88\x88 O(n\xc2\xb2) 包含检查。
\n\n每个集合的包含测试都是 O(d),因此是结果。
\n| 归档时间: |
|
| 查看次数: |
1118 次 |
| 最近记录: |