我正在尝试理解如何将树的最小尺寸顶点覆盖物作为动态编程问题来制定问题并且遇到一些麻烦。对我来说,涉及深度优先搜索的非动态编程公式具有最直观的意义。本质上,这涉及到叶节点执行DFS,包括最小尺寸顶点覆盖中的父节点,并重复到根节点。伪代码是这样的:
// DFS based solution
find_minimum_vertex_cover_dfs(root) {
// leaf nodes aren't in minimum size vertex cover
if (root == NULL || isLeafNode(root)) {
return;
}
for (each child node of root) {
find_minimum_vertex_cover_dfs(child node);
// child isn't in minimum size vertex cover so need to cover edge
// from current root to child by including current root
if (!isInMinCover(child node)) {
include root in minimum vertex cover;
}
}
}
我从here获得的动态编程公式如下:
DynamicVC(root):
for each child c:
Best[c][0], Best[c][1] = DynamicVC(c)
withoutRoot = sum over all c of Best[c][1]
withRoot = 1 + sum over all c of min(Best[c][0], Best[c][1])
return (withoutRoot, withRoot)
我想我理解子问题是计算根据每个顶点的子树的最小尺寸顶点覆盖的想法,包括封面中的顶点并从封面中排除该顶点。我有两个问题:
编辑:正如我想到的那样,可能令我困惑的是,在这种情况下,在树上递归地执行DFS是我更熟悉的。我一直在做一堆动态编程问题,但这是第一个涉及树/图遍历的问题,而在其他问题中,我可以使用一些循环来计算越来越大的子问题。我想通过使用显式堆栈并通过这种方式进行树遍历而不是通过递归,我可以使动态编程版本对我更熟悉。这有意义吗?
答案 0 :(得分:1)
1:没有什么好理由。它只是工作,所以为什么不使用它。对我来说,你所展示的DP解决方案比递归解决方案更直观。
2:动态编程是关于递归解决方案的子问题记忆。提出DP算法通常需要先定义递归,然后再添加memoization。递归解决方案可以自动转换为DP:只需创建类型为(subproblem id -> result)
的全局散列表,并在递归调用开始时检查散列映射是否已包含给定子问题的结果,如果是,则返回然后立即计算并将其放入hashmap中。这种方法通常与您提到的自下而上的方法一样快。