问题描述
假设我有集合S1,..,Sn,我们想找到 最小的盖子C1,..,Cm,因此在每个盖子中都不会相交 连接的组件。
例如,对于集合S1 = [X,Y],S2 = [Y,Z],S3 = [T] I 将找到封面C1 = [X,Y,Z]和C2 = [T]。关于什么 可以动态分割封面的动态算法?
假设元素Y死了,那么我们剩下 S1'= [X],S2'= [Z],S3'= [T]。现在是封面 C1'= [X],C2'= [Z],C3'= [T]。所以封面数 增加了。
联合查找算法可以确定给定的覆盖范围 集的集合,但恐怕每当元素死亡时重新计算完整集合的效率都不高。
解决方法
要利用Prolog系统的资源,我做了一个基于copy_term / 2和keysort / 2的联合查找算法。算法here的主要入口点执行以下操作:
<div id="CaixaGaleria" class="gallery">
<div data-tecnica="lapis" data-estilo="retrato" data-tamanho="pequeno"><img src="Imagens/Filme/14.jpg"></div>
<div data-tecnica="lapis" data-estilo="retrato" data-tamanho="medio"><img src="Imagens/Filme/16.jpg"></div>
<div data-tecnica="lapis" data-estilo="retrato" data-tamanho="grande"><img src="Imagens/Filme/17.jpg"></div>
<div data-tecnica="aguarela" data-estilo="paisagem" data-tamanho="pequeno"><img src="Imagens/Filme/20.jpg"></div>
<div data-tecnica="aguarela" data-estilo="paisagem" data-tamanho="medio"><img src="Imagens/Filme/22.jpg"></div>
<div data-tecnica="aguarela" data-estilo="paisagem" data-tamanho="grande"><img src="Imagens/Filme/27.jpg"></div>
<div data-tecnica="oleo" data-estilo="ilustração" data-tamanho="pequeno"><img src="Imagens/Filme/37.jpg"></div>
<div data-tecnica="oleo" data-estilo="ilustração" data-tamanho="medio"><img src="Imagens/Filme/42.jpg"></div>
<div data-tecnica="oleo" data-estilo="ilustração" data-tamanho="grande"><img src="Imagens/Filme/53.jpg"></div>
</div>
这是一个示例运行:
covers(L,S) :-
vars_list(L,K),copy_term(K,R),make_keys(L,R,H),keysort(H,J),collect_keys(J,S).
要获得动态算法,我们可以尝试以下方法。保持可追溯的结构,该结构允许从元素中查找封面。然后,如果某个元素死亡,则仅重新计算属于该死亡元素的掩盖。
这将稍微降低复杂度。否则,除了观察到死亡元素只会将其自身的封面分成较小的封面之外,我这里没有更好的主意了。
,我认为这有两个困难的部分:
- 非常有效的地图数据结构
- 一种有效的数据结构,用于跟踪要重新计算的内容
联合查找数据结构本身已在Prolog中实现,但隐藏在“变量”这个不太清晰的名称后面。如果您有一种将术语与代表其并集查找对等类的变量相关联的方法,则:
-
find
操作是对类变量的查找 - 两个类是否相同的测试是
Class1 == Class2
-
union
操作是Class1 = Class2
所以find
是瓶颈。在命令式语言中,查询通常是恒定时间,使用foo.equivalence_class
或equivalence_classes[foo.id]
之类的东西。通常,在Prolog中,我们没有等效的(近)恒定时间映射。但是由于您似乎只对变量感兴趣,所以SWI-Prolog的attributed variables确实适合您!
我们可以为像这样的术语列表计算联合查找对等类:
compute_classes([]).
compute_classes([Term | Terms]) :-
term_variables(Term,Variables),variables_class(Variables,_NewEquivalenceClass),compute_classes(Terms).
variables_class([],_EquivalenceClass).
variables_class([Var | Vars],EquivalenceClass) :-
( get_attr(Var,equivalence_class,ExistingEquivalenceClass)
-> ExistingEquivalenceClass = EquivalenceClass
; put_attr(Var,EquivalenceClass) ),variables_class(Vars,EquivalenceClass).
使用您的示例:
?- compute_classes([X+Y,Y+Z,T]).
put_attr(X,_2772),put_attr(Y,put_attr(Z,put_attr(T,_2814).
我们可以看到X
,Y
和Z
都共享一个等效类,而T
在一个单独的类中。
一些实用程序:
var_class(Var,Class) :-
get_attr(Var,Class).
var_var_sameclass(Var1,Var2) :-
var_class(Var1,Class1),var_class(Var2,Class2),Class1 == Class2.
var_var_union(Var1,Class1 = Class2.
继续示例:
?- compute_classes([X+Y,T]),var_class(X,ClassX),var_class(Y,ClassY),var_class(T,ClassT).
ClassX = ClassY,put_attr(X,ClassT).
?- compute_classes([X+Y,var_var_sameclass(X,Y).
put_attr(X,_3436),_3478).
?- compute_classes([X+Y,T).
false.
?- compute_classes([X+Y,var_var_union(Z,T),T).
put_attr(X,_3502),_3502).
也就是说,X
和Y
实际上属于同一类,而X
和T
不是同一类。如果我们合并Z
和T
的类,那么突然之间X
和T
属于同一类。
杀死变量变得更加乏味。这里的想法(如您在问题中所建议的)是仅重新计算输入的“受影响”部分。我认为这可以通过将一组受影响的术语与每个等价类相关联来完成。我将在此处使用列表,但我不建议列表在实践中是一个不错的选择。
计算术语列表的等效类,以及每个等效类的“监视列表”:
compute_classes_and_watchlists(Terms) :-
compute_classes(Terms),maplist(compute_watchlist,Terms).
compute_watchlist(Term) :-
term_variables(Term,[RepresentativeVariable | _OtherVars]),var_class(RepresentativeVariable,Class),( get_attr(Class,class_watchlist,Watchlist)
-> true
; Watchlist = [] ),put_attr(Class,[Term | Watchlist]).
例如:
?- compute_classes_and_watchlists([X+Y,_2932),put_attr(_2932,[Y+Z,X+Y]),_3012),put_attr(_3012,[T]).
因此,如果您要杀死X
,Y
或Z
中的任何一个,则他们的课程_2932
的监视列表将告诉您,您需要重新计算等效项术语Y+Z
和X+Y
的类(但没有其他内容)。
杀死自己会获取被杀死变量的类及其监视列表(“返回”)并清除该类中每个变量的等效类:
kill_var(Var,TermsToRecompute) :-
var_class(Var,get_attr(Class,TermsToRecompute),del_attr(Class,class_watchlist),maplist(clear_class,TermsToRecompute).
clear_class(Term) :-
term_variables(Term,del_attr(RepresentativeVariable,equivalence_class).
仅当您立即(a)将被杀死的变量绑定到基本术语,并且(b)重新计算受影响术语的等价类时,杀死才有意义。在您的示例中:
?- compute_classes_and_watchlists([X+Y,kill_var(Y,Y = y_is_now_bound,compute_classes_and_watchlists(TermsToRecompute).
Y = y_is_now_bound,TermsToRecompute = [y_is_now_bound+Z,X+y_is_now_bound],_4640),put_attr(_4640,[X+y_is_now_bound]),_4674),put_attr(_4674,[y_is_now_bound+Z]),_4708),put_attr(_4708,[T]).
这变得很难理解,但要点是,在杀死并绑定Y
并根据受影响的术语重新计算并发现结构后,X
和Z
现在处于单独的等效类。 T
只是不受影响。
尽管我对SWI归因于var docs的阅读是,这甚至都不会做任何非常错误的事情,但所有这些假设都假设您不会在一个联合中回溯。由于put_attr
是可回溯的,因此在更谨慎的情况下,这可能会成为相当灵活的实现,其中回溯只是将类再次分开。我认为,在一次杀戮中回溯也是可行的。
待办事项:
- 在监视列表的情况下,必须更改
var_var_union
的定义以合并两个类的监视列表(如果它们是不同的);对于实际的列表,这将是append
,但是某些实际的集合或更专业的数据结构会更好,特别是如果您期望具有某种“类似堆栈”的行为,而其中下一个变量很可能被杀死您最近对其进行过union
操作 的操作
- 尤其是使用监视列表方法,必须防止用户意外地统一
equivalence_class
术语;这可以通过使用某种class(<unique_id>,NakedClassVariable)
而不是仅使用裸变量来表示等价类来完成 - 重复调用
term_variables
可能可以通过某种方式进行优化-除了术语的监视列表之外,您还可以保留感兴趣的变量的监视列表
总而言之,这还不是生产准备就绪的代码,但可能会给您一些想法。