我试图在Prolog中实现一些图形算法。我想到了一个使用统一从图结构构建树的想法:
图形将定义如下:
Vertex-Variable
对的列表,其中Vertex
是代表顶点的常量,Variable
是对应的变量,将用作顶点的“引用” 。例如:
[a-A, b-B, c-C, d-D]
VertexVar-NeighboursList
对的列表,其中VertexVar
和NeighboursList
中的各个邻居是“参考变量”。例如:
[A-[B, C, D], B-[A, C], C-[A, B], D-[A]]
的意思是b
,c
,d
是a
等的邻居。
然后在某些可以使用从原始图构建的树的图算法(例如搜索组件或简单的DFS / BFS等)之前,可以使用诸如unify_neighbours
之类的谓词来统一{ {1}}与VertexVar-NeighbourList
配对。之后,顶点变量可以解释为邻居的列表,其中每个邻居又是邻居的列表。
因此遍历图时将获得良好的性能,因为不需要线性搜索图中每个顶点的某些顶点及其邻居。
但是我的问题是:如何比较那些顶点变量? (检查它们是否相同。)我尝试使用VertexVar = NeighboursList
,但存在一些冲突。对于上面的示例,(使用A == B
谓词),Prolog在内部将图形解释为:
unify_neighbours
其中:
[a-[S_1, S_2, S_3], b-S_1, c-S_2, d-S_3]
问题出在S_1 = [[S_1, S_2, S_3], S_2]
S_2 = [[S_1, S_2, S_3], S_1]
S_3 = [[S_1, S_2, S_3]]
是S_1
的情况下,S_2
和b
(又名c
和X = [something, Y], Y = [something, X], X == Y
)。共享相同邻居的顶点也会遇到同样的问题。例如true
和U-[A, B]
。
所以我的问题是:还有其他比较变量的方法可以帮助我吗?是比较“变量本身”而不是内容的东西,例如比较过程编程语言中的地址?还是太程序化而破坏了Prolog的声明性思想?
V-[A, B]
这个例子并没有真正起作用,但这就是想法。 (此外,检查是否找到了顶点是线性完成的,因此性能仍然不好,但这只是为了演示。)现在,为变量找到对应顶点的谓词实现为:
graph_component(Vertices, Neighbours, C) :-
% Vertices and Neighbours as explained above.
% C is some component found in the graph.
vertices_refs(Vertices, Refs),
% Refs are only the variables from the pairs.
unify_neighbours(Neighbours), % As explained above.
rec_(Vertices, Refs, [], C).
rec_(Vertices, Refs, Found, RFound) :-
% Vertices as before.
% Refs is a stack of the vertex variables to search.
% Found are the vertices found so far.
% RFound is the resulting component found.
[Ref|RRest] = Refs,
vertices_pair(Vertices, Vertex-Ref),
% Vertex is the corresponding Vertex for the Ref variable
not(member(Vertex, Found)),
% Go deep:
rec_(Vertices, Ref, [Vertex|Found], DFound),
list_revpush_result([Vertex|Found], DFound, Found1),
% Go wide:
rec_(Vertices, RRest, Found1, RFound).
rec_(Vertices, Refs, Found, []) :-
% End of reccursion.
[Ref|_] = Refs,
vertices_pair(Vertices, Vertex-Ref),
member(Vertex, Found).
vertices_pair([Vertex-Ref|_], Vertex-Ref).
vertices_pair([_-OtherRef|Rest], Vertex-Ref) :-
Ref \== OtherRef,
vertices_pair(Rest, Vertex-Ref).
运算符不是我真正想要的运算符,它会产生那些冲突。
答案 0 :(得分:3)
Prolog的固有功能是,将变量绑定到术语后,它就与术语本身变得难以区分。换句话说,如果将两个变量绑定到同一个术语,则将有两个相同的事物,并且无法将它们区分开。
适用于您的示例:将每个顶点变量与相应的邻居列表统一后,所有变量都消失了:您只剩下一个嵌套(并且很可能是圆形)数据结构,该数据结构由一个列表清单...
但是正如您所建议的,嵌套结构是一个吸引人的主意,因为它使您可以直接访问相邻节点。而且,尽管Prolog系统在支持循环数据结构的方式上有所不同,但这并不能阻止您利用这种想法。
设计的唯一问题是,一个节点仅由(可能深嵌套和圆形的)数据结构标识,该数据结构描述了可从其到达的子图。其结果是
一种简单的解决方法是在数据结构中包含唯一节点标识符(例如名称或数字)。要使用您的示例(稍作修改以使其更有趣):
make_graph(Graph) :-
Graph = [A,B,C,D],
A = node(a, [C,D]),
B = node(b, [A,C]),
C = node(c, [A,B]),
D = node(d, [A]).
然后您可以使用该标识符检查匹配的节点,例如进行深度优先遍历:
dfs_visit_nodes([], Seen, Seen).
dfs_visit_nodes([node(Id,Children)|Nodes], Seen1, Seen) :-
( member(Id, Seen1) ->
Seen2 = Seen1
;
writeln(visiting(Id)),
dfs_visit_nodes(Children, [Id|Seen1], Seen2)
),
dfs_visit_nodes(Nodes, Seen2, Seen).
样品运行:
?- make_graph(G), dfs_visit_nodes(G, [], Seen).
visiting(a)
visiting(c)
visiting(b)
visiting(d)
G = [...]
Seen = [d, b, c, a]
Yes (0.00s cpu)
答案 1 :(得分:0)
谢谢@jschimpf。它为我澄清了很多事情。我刚刚回到Prolog的一些图形问题,以为我会再次尝试这种递归数据结构,并提出了以下谓词来从边列表中构造该数据结构:
由@jschimpf提出的数据结构的“手动”创建:
my_graph(Nodes) :-
Vars = [A, B, C, D, E],
Nodes = [
node(a, [edgeTo(1, B), edgeTo(5, D)]),
node(b, [edgeTo(1, A), edgeTo(4, E), edgeTo(2, C)]),
node(c, [edgeTo(2, B), edgeTo(6, F)]),
node(d, [edgeTo(5, A), edgeTo(3, E)]),
node(e, [edgeTo(3, D), edgeTo(4, B), edgeTo(1, F)]),
node(e, [edgeTo(1, E), edgeTo(6, C)])
],
Vars = Nodes.
edgeTo(Weight, VertexVar)
代表某个顶点的边缘,并带有权重。权重只是为了表明可以针对任何其他信息进行自定义。 node(Vertex, [edgeTo(Weight, VertexVar), ...])
代表一个带有邻居的顶点。
一种更加“用户友好”的输入格式:
[edge(Weight, FromVertex, ToVertex), ...]
带有可选的顶点列表:
[Vertex, ...]
对于上面的示例:
[edge(1, a, b), edge(5, a, d), edge(2, b, c), edge(4, b, e), edge(6, c, f), edge(3, d, e), edge(1, e, f)]
此列表可以使用以下谓词转换为递归数据结构:
% make_directed_graph(+Edges, -Nodes)
make_directed_graph(Edges, Nodes) :-
vertices(Edges, Vertices),
vars(Vertices, Vars),
pairs(Vertices, Vars, Pairs),
nodes(Pairs, Edges, Nodes),
Vars = Nodes.
% make_graph(+Edges, -Nodes)
make_graph(Edges, Nodes) :-
vertices(Edges, Vertices),
vars(Vertices, Vars),
pairs(Vertices, Vars, Pairs),
directed(Edges, DiretedEdges),
nodes(Pairs, DiretedEdges, Nodes),
Vars = Nodes.
% make_graph(+Edges, -Nodes)
make_graph(Edges, Nodes) :-
vertices(Edges, Vertices),
vars(Vertices, Vars),
pairs(Vertices, Vars, Pairs),
directed(Edges, DiretedEdges),
nodes(Pairs, DiretedEdges, Nodes),
Vars = Nodes.
% make_directed_graph(+Vertices, +Edges, -Nodes)
make_directed_graph(Vertices, Edges, Nodes) :-
vars(Vertices, Vars),
pairs(Vertices, Vars, Pairs),
nodes(Pairs, Edges, Nodes),
Vars = Nodes.
这些谓词的二进制版本假定每个顶点只能从边列表中获得-图形中没有“无边”顶点。对于这些情况,三元版本会另外列出一个顶点。
make_directed_graph
假定输入边是有方向的,make_graph
假定它们是无方向的,因此它会沿相反的方向创建其他有向边:
% directed(+UndirectedEdges, -DiretedEdges)
directed([], []).
directed([edge(W, A, B)|UndirectedRest], [edge(W, A, B), edge(W, B, A)|DirectedRest]) :-
directed(UndirectedRest, DirectedRest).
要从边列表中获取所有顶点,
% vertices(+Edges, -Vertices)
vertices([], []).
vertices([edge(_, A, B)|EdgesRest], [A, B|VerticesRest]) :-
vertices(EdgesRest, VerticesRest),
\+ member(A, VerticesRest),
\+ member(B, VerticesRest).
vertices([edge(_, A, B)|EdgesRest], [A|VerticesRest]) :-
vertices(EdgesRest, VerticesRest),
\+ member(A, VerticesRest),
member(B, VerticesRest).
vertices([edge(_, A, B)|EdgesRest], [B|VerticesRest]) :-
vertices(EdgesRest, VerticesRest),
member(A, VerticesRest),
\+ member(B, VerticesRest).
vertices([edge(_, A, B)|EdgesRest], VerticesRest) :-
vertices(EdgesRest, VerticesRest),
member(A, VerticesRest),
member(B, VerticesRest).
为每个顶点构造未初始化的变量:
% vars(+List, -Vars)
vars([], []).
vars([_|ListRest], [_|VarsRest]) :-
vars(ListRest, VarsRest).
要配对顶点和顶点变量:
% pairs(+ListA, +ListB, -Pairs)
pairs([], [], []).
pairs([AFirst|ARest], [BFirst|BRest], [AFirst-BFirst|PairsRest]) :-
pairs(ARest, BRest, PairsRest).
要构建递归节点:
% nodes(+Pairs, +Edges, -Nodes)
nodes(Pairs, [], Nodes) :-
init_nodes(Pairs, Nodes).
nodes(Pairs, [EdgesFirst|EdgesRest], Nodes) :-
nodes(Pairs, EdgesRest, Nodes0),
insert_edge(Pairs, EdgesFirst, Nodes0, Nodes).
首先,初始化每个顶点的空节点列表:
% init_nodes(+Pairs, -EmptyNodes)
init_nodes([], []).
init_nodes([Vertex-_|PairsRest], [node(Vertex, [])|NodesRest]) :-
init_nodes(PairsRest, NodesRest).
然后将边缘一一插入:
% insert_edge(+Pairs, +Edge, +Nodes, -ResultingNodes)
insert_edge(Pairs, edge(W, A, B), [], [node(A, [edgeTo(W, BVar)])]) :-
vertex_var(Pairs, B, BVar).
insert_edge(Pairs, edge(W, A, B), [node(A, EdgesTo)|NodesRest], [node(A, [edgeTo(W, BVar)|EdgesTo])|NodesRest]) :-
vertex_var(Pairs, B, BVar).
insert_edge(Pairs, edge(W, A, B), [node(X, EdgesTo)|NodesRest], [node(X, EdgesTo)|ResultingNodes]) :-
A \= X,
insert_edge(Pairs, edge(W, A, B), NodesRest, ResultingNodes).
要获取给定顶点的顶点变量:(这实际上在两个方向上都有效。)
% vertex_var(+Pairs, +Vertex, -Var)
vertex_var(Pairs, Vertex, Var) :-
member(Vertex-Var, Pairs).
```Prolog
This, of course, brings additional time overhead, but you can do this once and then just copy this data structure every time you need to perform some graph algorithm on it and access neighbours in constant time.
You can also add additional information to the `node` predicate. For example:
```Prolog
node(Vertex, Neighbours, OrderingVar)
例如,其中未初始化变量OrderingVar
可以在恒定时间内使用有关图的部分顺序中的顶点位置的信息来“分配”(初始化)。因此,可以将其用作输出。 (如Prolog注释中有时用+-
表示的-作为输入项一部分的未初始化变量,尚未由使用的谓词初始化并提供输出。)