给定有向图,目标是将节点与其指向的节点相结合,并提出最小数量的这些[让名称] 超级节点。
一旦您组合了节点,您就无法再次使用这些节点。 [第一个节点以及所有组合节点 - 即一个超级节点的所有成员]
贪婪的方法是选择具有最大出度的节点,并将该节点与其指向的节点组合并删除所有节点。每次使用尚未从图中删除的节点执行此操作。
贪婪是O(V),但这不一定会输出最小数量的超级节点。 那么这样做的最佳算法是什么?
答案 0 :(得分:2)
我会尝试以不同的方式表征问题。您想将顶点分成两组。第一组由“根节点”组成,第二组由“从属节点”组成,即直接连接到根节点的节点。
root dependent B A < C E D < F
图。 0:可能的结果图。
您希望最小化根节点的数量。这与最大化从属节点的数量相同,这与最大化结果图中的边数相同,这与从开始图构造它时删除的边数最小化相同。
让我们首先看一下暴力:对于节点的每个二分区分为根和依赖集,首先检查它是否符合问题陈述的标准,最后采取尽可能好的方法。有两个指数的分区,因此必须进行改进。如果我们认为每条边具有可能的状态“未知”,“取”或“移除”,则取边缘将移除源自其末端节点的所有边缘,以及在此处结束的所有边缘(参见图1) )。但是,无论如何,我们最多可以保留一个以特定节点结尾的边缘。
A B C \|/ D /|\ E F G
图1:取边缘A-D移除此处所有其他边缘。
有一些“贪婪”的启发式方法:
这就存在一些问题,即一些连接的节点会更好地放入根集中,这将我们带到第一个细化:
这看起来很不错,但仍不一定是最佳的。也许你可以从另一方开始:
我仍然无法证明这是否是最佳的,但至少我不能直接想到破坏它的情况。
答案 1 :(得分:1)
20!相当大,大于2 ^ 61。幸运的是,有一种更好的方法来解决小实例:(EDIT)动态编程。通过为每个子问题保存最佳解决方案,我们会花费一些内存来节省大量时间。
这是Python中的一些示例代码。在用另一种语言实现下面的代码时,你可能想要对顶点0,...,n-1进行编号,并将这些集实现为位向量。
# find a smallest node closure of G
# G is a graph in adjacency-list format: G[v] is the set of neighbors of v
def node_closure(G):
# maps subsets of vertices to their smallest node closure
smallest = {frozenset(): []}
def find_smallest(S):
if S in smallest:
return smallest[S]
else:
candidates = [[v] + find_smallest(S - frozenset([v]) - G[v]) for v in S]
return min(candidates, key=len)
return find_smallest(frozenset(G))
此问题从设置封面的NP硬度降低,保留了目标值。这意味着除非P = NP,否则您可以获得的多项式时间算法的最佳保证是它始终输出的解决方案最多比最优的O(log n)
倍。
如果x1, ..., xm
是要涵盖的元素且S1, ..., Sn
是集合,则集合覆盖的目标是选择其并集为{x1, ..., xm}
的最小集合数。假设每个元素至少出现在一个集合中,请创建一个包含节点x1, ..., xm, S1, ..., Sn, R
的图表,其中有R
到所有Si
和所有i, j
的弧,来自Si
至xj
当且仅当xj
属于Si
时。节点闭包和集合封面之间有直接的对应关系:从集合封面获取节点闭包,删除与所选集合对应的顶点,然后R
;要从节点闭包中获取集合封面,请选择所有选择顶点的集合以及包含其顶点所选的每个xj
的集合。
(注意,对于集合覆盖,贪婪算法可以达到最佳逼近比率!类似的问题可能与您的问题类似。)