Какие структуры данных использовать для алгоритма Дейкстры в Erlang? - PullRequest
6 голосов
/ 17 июля 2011

Отказ от ответственности: автор является новичком в Erlang.

Представьте себе, у нас есть граф, состоящий из 1M узлов, и каждый узел имеет 0-4 соседей (ребра происходят изкаждый узел связан с этими соседями, поэтому график направлен и связан).

Вот мой выбор структур данных:

Для хранения графика я использую орграф, основанный на таблицах ETS.Это обеспечивает быстрый (O (1)) доступ к соседям узла.

Для списка не посещенных узлов я использую gb_sets: take_smallest (узел уже отсортирован, и он одновременно удаляется после выборки).

Для списка предшественников я использую структуру dict, которая позволяет хранить предшественники следующим образом: {Node1, Node1_predecessor}, {Node2, Node2_predecessor}.

Для спискапосещенных узлов я использую простой список.

Проблемы:

  1. Код становится очень трудно читать и поддерживать, когда я пытаюсь обновить вес узла как в структуре орграфа.и в структуре Unvisited_nodes.Не представляется правильным способ сохранить один «объект» с «полями», которые необходимо обновлять в двух структурах данных одновременно.Как правильно это сделать?
  2. Тот же вопрос о списке предшественников.Где я должен хранить предшествующее «поле» узла «объект»?Может быть, в графе (структура орграфа)?
  3. Может быть, мне следует переосмыслить весь алгоритм Дейкстры в терминах процессов и сообщений вместо объектов (узлов и ребер) и их полей (весов)?

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.

1 Ответ

1 голос
/ 18 июля 2011

Ваш выбор структур данных выглядит хорошо, так что в основном вопрос в том, что в них вставить и как их поддерживать в актуальном состоянии.Я бы предложил следующее (я немного изменил имена):

  • Result: A dict отображение Node на {Cost, Prev}, где Costобщая стоимость пути к Node и Prev является его предшественником на пути.

  • 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} к Open1, но только если NextNode еще не в Result.Продолжайте с Open1, пока набор не станет пустым.

Алгоритм Дейкстры запрашивает операцию decrease_key() на наборе Open.Так как gb_sets не поддерживает это, мы использовали обходной путь вставки кортежа для NextNode, даже если NextNode уже может присутствовать в Open.Вот почему мы проверяем, находится ли узел, извлеченный из Open, уже в Result.


Расширенное обсуждение использования очереди приоритетов

ТамЕсть несколько способов использования очереди приоритетов с алгоритмом Дейкстры.

В стандартной версии Википедии узел v вставляется только один раз, но позиция v обновляется, когдастоимость и предшественник v изменены.

alt := dist[u] + dist_between(u, v)
if alt < dist[v]:
    dist[v] := alt
    previous[v] := u
    decrease-key v in Q

Реализации часто упрощаются, заменяя decrease-key v in Q на add v to Q.Это означает, что v может быть добавлено более одного раза, и поэтому алгоритм должен проверить, что u, извлеченный из очереди, еще не добавлен к результату.

В моей версии я заменяювесь блок выше с add v to Q.Следовательно, очередь будет содержать еще больше записей, но поскольку они всегда извлекаются в порядке, это не влияет на правильность алгоритма.Если вам не нужны эти дополнительные записи, вы можете использовать словарь для отслеживания минимальной стоимости для каждого узла.

...