ska*_*tek 6 erlang directed-graph dijkstra shortest-path
免责声明:作者是Erlang的新手.
想象一下,我们有一个由1M个节点组成的图形,每个节点有0-4个邻居(边缘从每个节点发出到那些邻居,因此图形是定向和连接的).
这是我选择的数据结构:
为了存储图形,我使用了基于ETS表的有向图.这允许快速(O(1))访问节点的邻居.
对于未访问节点的列表,我使用gb_sets:take_smallest(节点已经排序,并且在获取后同时删除).
对于前趋列表,我使用dict结构,它允许以下列方式存储前驱:{Node1,Node1_predecessor},{Node2,Node2_predecessor}.
对于访问节点列表,我使用一个简单的列表.
问题:
UPD:
以下是基于Antonakos建议的代码:
dijkstra(Graph,Start_node_name) ->
io:format("dijkstra/2: start~n"),
Paths = dict:new(),
io:format("dijkstra/2: initialized empty Paths~n"),
Unvisited = gb_sets:new(),
io:format("dijkstra/2: initialized empty Unvisited nodes priority queue~n"),
Unvisited_nodes = gb_sets:insert({0,Start_node_name,root},Unvisited),
io:format("dijkstra/2: Added start node ~w with the weight 0 to the Unvisited nodes: ~w~n", [Start_node_name, Unvisited_nodes]),
Paths_updated = loop_through_nodes(Graph,Paths,Unvisited_nodes),
io:format("dijkstra/2: Finished searching for shortest paths: ~w~n", [Paths_updated]).
loop_through_nodes(Graph,Paths,Unvisited_nodes) ->
%% We need this condition to stop looping through the Unvisited nodes if it is empty
case gb_sets:is_empty(Unvisited_nodes) of
false ->
{{Current_weight,Current_name,Previous_node}, Unvisited_nodes_updated} = gb_sets:take_smallest(Unvisited_nodes),
case dict:is_key(Current_name,Paths) of
false ->
io:format("loop_through_nodes: Found a new smallest unvisited node ~w~n",[Current_name]),
Paths_updated = dict:store(Current_name,{Previous_node,Current_weight},Paths),
io:format("loop_through_nodes: Updated Paths: ~w~n",[Paths_updated]),
Out_edges = digraph:out_edges(Graph,Current_name),
io:format("loop_through_nodes: Ready to iterate through the out edges of node ~w: ~w~n",[Current_name,Out_edges]),
Unvisited_nodes_updated_2 = loop_through_edges(Graph,Out_edges,Paths_updated,Unvisited_nodes_updated,Current_weight),
io:format("loop_through_nodes: Looped through out edges of the node ~w and updated Unvisited nodes: ~w~n",[Current_name,Unvisited_nodes_updated_2]),
loop_through_nodes(Graph,Paths_updated,Unvisited_nodes_updated_2);
true ->
loop_through_nodes(Graph,Paths,Unvisited_nodes_updated)
end;
true ->
Paths
end.
loop_through_edges(Graph,[],Paths,Unvisited_nodes,Current_weight) ->
io:format("loop_through_edges: No more out edges ~n"),
Unvisited_nodes;
loop_through_edges(Graph,Edges,Paths,Unvisited_nodes,Current_weight) ->
io:format("loop_through_edges: Start ~n"),
[Current_edge|Rest_edges] = Edges,
{Current_edge,Current_node,Neighbour_node,Edge_weight} = digraph:edge(Graph,Current_edge),
case dict:is_key(Neighbour_node,Paths) of
false ->
io:format("loop_through_edges: Inserting new neighbour node ~w into Unvisited nodes~n",[Current_node]),
Unvisited_nodes_updated = gb_sets:insert({Current_weight+Edge_weight,Neighbour_node,Current_node},Unvisited_nodes),
io:format("loop_through_edges: The unvisited nodes are: ~w~n",[Unvisited_nodes_updated]),
loop_through_edges(Graph,Rest_edges,Paths,Unvisited_nodes_updated,Current_weight);
true ->
loop_through_edges(Graph,Rest_edges,Paths,Unvisited_nodes,Current_weight)
end.
Run Code Online (Sandbox Code Playgroud)
您选择的数据结构看起来不错,因此主要是在其中插入什么内容以及如何使它们保持最新的问题。我建议如下(我稍微更改了名称):
Result:到 的dict映射,其中是到 的路径的总成本,是该路径上的前身。Node{Cost, Prev}CostNodePrev
Open:gb_sets的结构{Cost, Node, Prev}。
具有 形边的图形{EdgeCost, NextNode}。
搜索结果由 表示Result,图表根本不更新。没有多重处理或消息传递。
算法如下:
插入{0, StartNode, Nil},Open其中Nil是标记路径结束的内容。
让{{Cost, Node, Prev}, Open1} = gb_sets:take_smallest(Open)。如果Node已经存在Result则不执行任何操作;否则添加{Cost, Node, Prev}到Result,并为添加到{EdgeCost, NextNode}的每个边,但前提是尚未在 中。继续,直到集合为空。Node{Cost + EdgeCost, NextNode, Node}Open1NextNodeResultOpen1
Dijkstra 算法要求对集合decrease_key()进行操作Open。由于这不容易得到支持,因此gb_sets我们使用了插入元组的解决方法,NextNode即使NextNode元组可能已经存在Open。这就是为什么我们检查从中提取的节点是否Open已经在 中Result。
优先级队列的使用扩展讨论
有多种方法可以通过 Dijkstra 算法使用优先级队列。
在维基百科的标准版本中,节点仅插入一次,但当的成本和前身发生更改时,v的位置会更新。vv
alt := dist[u] + dist_between(u, v)
if alt < dist[v]:
dist[v] := alt
previous[v] := u
decrease-key v in Q
Run Code Online (Sandbox Code Playgroud)
实现通常通过替换为 来decrease-key v in Q简化add v to Q。这意味着v可以多次添加,因此算法必须检查u从队列中提取的内容是否尚未添加到结果中。
在我的版本中,我将上面的整个块替换为add v to Q. 因此,队列将包含更多条目,但由于它们总是按顺序提取,因此不会影响算法的正确性。如果您不需要这些额外的条目,您可以使用字典来跟踪每个节点的最低成本。