Эффективный алгоритм для поиска всех путей от A до Z?

С набором случайных входов, как это (20k строк):

A B
U Z
B A
A C
Z A
K Z
A Q
D A
U K
P U
U P
B Y
Y R
Y U
C R
R Q
A D
Q Z

Найдите все пути от A до Z.

  • A - B - Y - R - Q - Z
  • A - B - Y - U - Z
  • A - C - R - Q - Z
  • A - Q - Z
  • A - B - Y - U - K - Z

enter image description here

Местоположение не может появляться более одного раза в пути, поэтому A - B - Y - U - P - U - Z недействителен.

Местоположения называются AAA для ZZZ (для простоты представлены здесь как A - Z), и вход случайен таким образом, что может быть или не быть местоположение ABC, все местоположения могут быть XXX (маловероятными), или там не может быть возможным путем во всех местах "изолированы".

Изначально я думал, что это вариация невзвешенной кратчайшего пути, но я нахожу ее совсем другой, и я не знаю, как здесь применяется алгоритм.

Мое текущее решение выглядит следующим образом:

  • Предварительно обработайте список таким образом, чтобы мы имели хэш-карту, которая указывает местоположение (слева), на список местоположений (справа)

  • Создайте хэш-карту, чтобы отслеживать "посещенные местоположения". Создайте список для хранения найденных путей.

  • Хранить X (начальное местоположение) в hashmap "посещенных мест".

  • Найдите X в первом хэшмапе, (местоположение A даст нам (B, C, Q) в O (1) раз).

  • Для каждого найденного местоположения (B, C, Q) проверьте, является ли он конечным пунктом назначения (Z). Если это сохранить его в списке "найденные пути". Иначе, если он еще не существует в hashmap "посещенных мест", Recurl до шага 3 теперь с этим местом как "X". (фактический код ниже)

В этом текущем решении требуется навсегда сопоставить все (не кратчайшие) возможные маршруты от "BKI" до "SIN" для это предоставило данные.

Мне было интересно, есть ли более эффективный (временный) способ сделать это. Кто-нибудь знает лучший алгоритм, чтобы найти все пути от произвольной позиции A до произвольной позиции Z?

Фактический код для текущего решения:

import java.util.*;
import java.io.*;

public class Test {
    private static HashMap<String, List<String>> left_map_rights;

    public static void main(String args[]) throws Exception {
        left_map_rights = new HashMap<>();
        BufferedReader r = new BufferedReader(new FileReader("routes.text"));
        String line;
        HashMap<String, Void> lines = new HashMap<>();
        while ((line = r.readLine()) != null) {
            if (lines.containsKey(line)) { // ensure no duplicate lines
                continue;
            }
            lines.put(line, null);
            int space_location = line.indexOf(' ');
            String left = line.substring(0, space_location);
            String right = line.substring(space_location + 1);
            if(left.equals(right)){ // rejects entries whereby left = right
                continue;
            }
            List<String> rights = left_map_rights.get(left);
            if (rights == null) {
                rights = new ArrayList<String>();
                left_map_rights.put(left, rights);
            }
            rights.add(right);
        }
        r.close();
        System.out.println("start");
        List<List<String>> routes = GetAllRoutes("BKI", "SIN");
        System.out.println("end");
        for (List<String> route : routes) {
            System.out.println(route);
        }
    }

    public static List<List<String>> GetAllRoutes(String start, String end) {
        List<List<String>> routes = new ArrayList<>();
        List<String> rights = left_map_rights.get(start);
        if (rights != null) {
            for (String right : rights) {
                List<String> route = new ArrayList<>();
                route.add(start);
                route.add(right);
                Chain(routes, route, right, end);
            }
        }
        return routes;
    }

    public static void Chain(List<List<String>> routes, List<String> route, String right_most_currently, String end) {
        if (right_most_currently.equals(end)) {
            routes.add(route);
            return;
        }
        List<String> rights = left_map_rights.get(right_most_currently);
        if (rights != null) {
            for (String right : rights) {
                if (!route.contains(right)) {
                    List<String> new_route = new ArrayList<String>(route);
                    new_route.add(right);
                    Chain(routes, new_route, right, end);
                }
            }
        }
    }
}

Ответ 1

Насколько я понимаю ваш вопрос, алгоритм Дейкстраса не может применяться как есть, поскольку кратчайшая проблема пути для определения находит один путь в наборе всех возможных путей. Ваша задача - найти все пути для каждого.

Многие оптимизации по алгоритму Дейкстры включают в себя отсечение деревьев поиска с более высокими затратами. Вы не сможете отрезать эти части в своем поиске, так как вам нужны все находки.

И я предполагаю, что вы имеете в виду все пути, исключая круги.

Алгоритм:

  • Направьте сеть в массив 2dim 26x26 логического/целого числа. FromTo [I, J]. Установите 1/true для существующей ссылки.

  • Начиная с первого node отслеживать все следующие узлы (поисковые ссылки для 1/true).

  • Сохранять посещенные узлы в некоторой структуре (массив/список). Поскольку максимальный глубина кажется 26, это должно быть возможно через рекурсию.

  • И как написал @soulcheck ниже, вы можете подумать о разрезе путей, которые вы открыли. Вы можете сохранить список путей к месту назначения в каждом элементе массива. Соответственно отрегулируйте условие нарушения.

  • Перерыв при

    • посещение конца node (сохранить результат)
    • при посещении node, который был показан ранее (круг)
    • посещение node, для которого вы уже нашли все пути к получателю и объедините свой текущий путь со всеми существующими из этого node.

Производительность мудрая Я проголосовал против использования hashmaps и списков и предпочитаю статические структуры.

Хмм, перечитывая вопрос, я понял, что имя узлов не может быть ограничено A-Z. Вы пишете что-то о линиях 20k, с 26 буквами, полностью подключенная сеть A-Z потребует гораздо меньше ссылок. Возможно, вы пропустите рекурсию и статические структуры:)

Хорошо, с действительными именами от AAA до ZZZ массив станет слишком большим. Поэтому вам лучше создать динамическую структуру для сети. Вопрос счетчика: что касается производительности, какова лучшая структура данных для менее популярного массива, как того требует мой алгоритм? Я проголосую за 2 dim ArrayList. Кто-нибудь?

Ответ 2

То, что вы предлагаете, представляет собой схему для DFS, только с обратным трассировкой. Правильно, если вы не хотите разрешать циклические пути (вы не указали, если вы это делаете).

Однако есть две ошибки.

  • Вам нужно следить за узлами, которые вы уже посетили на текущем пути (для устранения циклов)
  • Вы должны знать, как выбрать следующий node при обратном отслеживании, чтобы вы не опускались на одно и то же поддерево в графике, когда вы уже посетили его по текущему пути.

Псевдокод более или менее выглядит следующим образом:

getPaths(A, current_path) :
    if (A is destination node): return [current_path]
    for B = next-not-visited-neighbor(A) : 
        if (not B already on current path) 
            result = result + getPaths(B, current_path + B)
    return result 

 list_of_paths =  getPaths(A, [A])

что почти что вы сказали.

Будьте осторожны, так как поиск всех путей в полном графике - это довольно много времени и памяти.

изменить Для уточнения алгоритм имеет сложность времени Ω (n!) В худшем случае, так как он должен перечислить все пути от одной вершины к другой в полном графике размера n, и есть как минимум (n-2)! пути формы < A, перестановки всех узлов, кроме A и Z, Z > . Невозможно сделать это лучше, если только перечисление результата займет столько же.

Ответ 3

Ваши данные по существу представляют собой список смежности, который позволяет вам построить дерево, внедренное в node, соответствующее A. Чтобы получить все пути между A и Z, вы можете запустить любой алгоритм обхода дерева.

Конечно, когда вы строите дерево, вы должны убедиться, что вы не вводите циклы.

Ответ 4

Я бы продолжил рекурсивно, где я бы построил список всех возможных путей между парами узлов all.

Я бы начал с построения для всех пар (X, Y) списка L_2 (X, Y), который является списком путей длины 2, которые идут от X до Y; что тривиально построить, так как список входных данных вам предоставляется.

Затем я бы реконструировал списки L_3 (X, Y), используя известные списки L_2 (X, Z) и L_2 (Z, Y), перейдя по Z. Например, для (C, Q) вам нужно попробовать все Z в L_2 (C, Z) и L_2 (Z, Q), и в этом случае Z может быть только R и вы получите L_3 (C, Q) = {C → R → Q}. Для других пар у вас может быть пустой L_3 (X, Y), или может быть много путей длины 3 от X до Y. Однако здесь вы должны быть осторожны при построении путей, поскольку некоторые из них должны быть отклонены, поскольку они имеют циклы. Если путь имеет два раза тот же node, он отклоняется.

Затем вы создаете L_4 (X, Y) для всех пар, комбинируя все пути L_2 (X, Z) и L_3 (Z, Y), перебирая все возможные значения для Z. Вы все равно удаляете пути с циклами.

И так далее... пока не дойдете до L_17576 (X, Y).

Один из них беспокоится о том, что у вас может быть нехватка памяти для хранения этих списков. Однако обратите внимание, что после вычисления L_4 вы можете избавиться от L_3 и т.д. Конечно, вы не хотите удалять L_3 (A, Z), поскольку эти пути являются допустимыми путями от A до Z.

Подробности реализации: вы можете поместить L_3 (X, Y) в массив 17576 x 17576, где элемент at (X, Y) - это некоторая структура, которая хранит все пути между (X, Y). Однако, если большинство элементов пустые (без путей), вы можете использовать вместо этого HashMap<Pair, Set<Path>>, где Pair - это просто некоторый объект, который хранит (X, Y). Мне не ясно, если большинство элементов L_3 (X, Y) пусты, и если да, то это также имеет место для L_4334 (X, Y).

Благодаря @Lie Ryan для указания этого идентичного question на mathoverflow. Мое решение в основном таково MRA; Хуан утверждает, что он недействителен, но, удалив пути с дублирующими узлами, я думаю, что мое решение в порядке.

Я думаю, что мое решение требует меньше вычислений, чем подход грубой силы, однако для этого требуется больше памяти. Настолько, что я даже не уверен, что это возможно на компьютере с разумным объемом памяти.