我们有一个DAG。我们在节点上有一个函数(松散地说,我们为节点编号)。我们想使用这些规则创建一个新的有向图:
- 只有具有相同编号的节点才能签到相同的新节点。。(但是,。)
- 我们在新节点之间添加所有旧边:。
- 此新图仍然是DAG。
| V'|的最小值是多少??创建最小新图的算法是什么?
我们有一个DAG。我们在节点上有一个函数(松散地说,我们为节点编号)。我们想使用这些规则创建一个新的有向图:
| V'|的最小值是多少??创建最小新图的算法是什么?
Answers:
解决此问题的一种方法是使用整数线性规划(ILP)。让我们解决问题的决策版本:给定,有没有办法收缩同色顶点以获得大小的DAG ?
可以使用标准技术将其表示为ILP实例。我们在原始图形中获得了每个顶点的颜色。我建议我们用的标签来标记每个顶点;具有相同标签和相同颜色的所有顶点将被收缩。因此,决策问题就变成了:是否存在标签,从而使所有相同颜色的相同标签顶点收缩会产生DAG?
要将其表示为整数线性程序,为每个顶点引入一个整数变量,以表示顶点上的标签。添加不等式。
下一步是表达要求收缩图必须是DAG的要求。请注意,如果存在上面列出的形式的标签,而又不失一般性,则存在这样的标签,其中标签在收缩图上引起拓扑排序(即,如果在收缩图中位于之前,则的标签小于的标签)。因此,对于原始图形中的每个边,我们将添加以下约束:和具有相同的标签和相同的颜色,否则的标签小于的标签。具体来说,对于每个边在具有相同颜色的初始图中,添加不等式。对于每个边缘,其中具有不同的颜色,添加不等式。
现在看看该整数线性程序是否有可行的解决方案。当且仅当标签具有所需的形式(即,收缩所有相同颜色的相同标签的顶点会产生DAG)时,才会有可行的解决方案。换句话说,当且仅当有一种方法可以将原始图压缩为大小为的DAG时,才会有可行的解决方案。我们可以使用任何整数线性规划求解器;如果ILP求解器给了我们答案,那么我们就可以解决原始决策问题。
当然,这不能保证在多项式时间内完成。没有保证。但是,ILP求解器已经相当不错。我希望对于一个合理大小的图,您有很大的机会使ILP求解程序能够在合理的时间内解决此问题。
也可以将其编码为SAT实例并使用SAT解算器。我不知道这是否会更有效。不过,ILP版本可能更容易考虑。
(我希望这是正确的。我没有仔细检查每一个细节,所以请仔细检查我的推理!我希望我没有在任何地方出现问题。)
更新(10/21):通过以拓扑排序的顺序处理DAG并跟踪每个顶点的标签下限,看起来这种形式的ILP可以在线性时间内解决。这使我怀疑自己的解决方案:我在某处犯了错误吗?
注意:AFAICT,DW在此减少中发现了一个漏洞,这是错误的(请参阅注释)。由于历史原因,将其保留在此处。
简介:首先,我将把单调3SAT问题简化为我们的问题。尽管单调3SAT问题可以轻松解决,但我们的问题可以进一步解决NP难的最小纯单调3SAT问题;因此,这个问题很难解决。
我们有一个单调布尔表达式,表示为一系列变量和一系列子句。CNF的形式为,使得:
和
我们构造一个图。G '中的每个顶点都有一个标签;具有相同标签的顶点可以收缩。
首先,我们构建了曲线图,如下所示:每个,我们提出两个节点,每个标记的X 我,以及从一个到另一个的有向边(点击用于高分辨率视图图像)。
这些节点当然可以收缩,因为它们具有相同的标签。我们将考虑将收缩的变量/节点的值为false,将未收缩的变量/节点的值为true:
这一步后,应该包含2 ·&| V | 节点。接下来,我们介绍子句约束。对于每个子句,Ç 我 ∈ Ç,Ç 我 = (X Ĵ ∨ X ķ ∨ X 升)| X Ĵ,X ķ,X 升 ∈ V,我们引入一个节点Ç 我,和以下边:
这是另一个可视化,展开了子句约束:
因此,每个子句约束要求它包含的变量中的至少一个保持不变。由于未签约的节点的值为true,因此这要求变量之一为true。正是Monotone SAT的条款所需要的。
单调3SAT可以轻松满足;您只需将所有变量设置为true。
但是,由于我们的DAG最小化问题是找到最多的收缩,因此这转化为找到在CNF中产生最多错误变量的令人满意的赋值。这与找到最小的真实变量相同。该问题有时称为最小真实单调3SAT或此处(作为优化问题或决策问题),或称为k-True单调2SAT(作为较弱的决策问题);都是NP难题。因此,我们的问题是NP难题。
参考文献:
图表来源:
每次替换(亲子直接替换除外)时,您都添加了新的祖先后代关系,从而可以轻松地确定哪一个长期值得。因此,一般情况下,简单的贪婪算法将失败。但是,如果您采用蛮力方法,则可以确定最小的图形:
python风格(未经测试):
def play((V,E),F,sequence=[]):
"""
(V,E) -- a dag.
V -- a set of vertices.
E -- a set of directed-edge-tuples.
F -- a function that takes a vertex, returns an integer.
sequence -- the sequence of moved taken so far; starts with/defaults to
an empty list, will contain tuples of the form (x,y)
where x is removed and replaced with y.
Returns the best recursively found solution.
"""
#find all the integer values in the graph, remember which
# values correspond to what vertices. Of the form {integer => {vertices}}.
n2v = {}
for x in V:
n = F(x)
#for each integer, make sure you have a set to put the vertices in.
if n not in n2v:
n2v[n] = set()
#for each integer, add the vertex to the equivalent set.
n2v[n].add(v)
#record the best sequence/solution. You start with the current sequence,
# and see if you can obtain anything better.
best_solution = list(sequence)
#Now you will try to combine a single pair of vertices, obtain a new
# graph and then recursively play the game again from that graph.
#for each integer and equivalent set of vertices,
for n,vset in n2v.iteritems():
#pick a pair of vertices
for x in vset:
for y in vset:
#no point if they are the same.
if x == y:
continue
#If there is a path from x => y or y => x, then you will be
# introducing a cycle, breaking a rule. So in that case, disregard
# this pair.
#However, the exception is when one is a direct child of the other;
# in that case you can safely combine the vertices.
if pathtest((V,E),x,y) and (x,y) not in E and (x,y) not in E:
continue
#combine the vertices (function is defined below), discard x,
# replace it with y, obtain the new graph, (V',E').
Vp,Ep = combine_vertex((V,E),x,y))
#record the sequence for this move.
sequencep = list(sequence) + [(x,y)]
#recurse and play the game from this new graph.
solution = play(Vp,Ep,F,sequencep)
#if the returned solution is better than the current best,
if len(solution) > len(best_solution):
#record the new best solution
best_solution = solution
#return the best recorded solution
return best_solution
def combine_vertex((V0,E0),x,y):
"""
(V0,E0) -- an initial digraph.
V0 -- a set of vertices.
E0 -- a set of directed-edge-tuples.
x -- vertex to discard.
y -- vertex to replace it with.
returns a new digraph replacing all relationships to and from x to relate
to y instead, and removing x from the graph entirely.
"""
#the final vertex set will have everything except x
V = set(V0)
V.discard(x)
#now you construct the edge set.
E = set()
#for every edge,
for (u0,v0) in E0:
#recreate the edge in the new graph, but replace any occurence
# of x.
u,v = u0,v0
#if x is in the edge: replace it
if u == x:
u = y
if v == x:
v == y
#sometimes u=v=y and can now be pointing to itself, don't add that
# edge
if u == v:
continue
#add the new/replaced edge into the edge-set.
E.add( (u,v) )
return (V,E)
我不确定这是否真的是一个难题,但是手动操作一些图形似乎很容易组合。我很好奇是否可以将一些困难减少到此问题,或者是否有一种算法可以缩短运行时间。