Построение алгоритма всех путей для DAG

Пытался создать метод, который получает все мыслимые уникальные пути через DAG. Пошел с рекурсией, потому что это казалось самым простым для понимания. Закончилось этим:

public class Brutus {
    //the previous nodes visited
    public ArrayList<Node> resultHistory = new ArrayList<Node>();
    //Directed Graph class, contains a HashMap [adjacencies]
    // that has keys for all nodes that contains all edges
    public AdjacencyList paths;
    //A list of all the pathways between nodes represented as a list of nodes
    public ArrayList<ArrayList<Node>> allPaths = new ArrayList<ArrayList<Node>>();

public Brutus(AdjacencyList paths) {
    this.paths = paths;
}

public ArrayList<ArrayList<Node>> findAll() {
    int counter = 1;
    for (Node key : paths.adjacencies.keySet()) {
        System.out.println("[" + counter + "]: " + key.toString());
        StringTokenizer st = new StringTokenizer(
            paths.getAdjacentString(key), ",");
        while (st.hasMoreTokens()) {
            String child = st.nextToken();
            if (paths.getNodeFromGraph(child) != null) {
                resultHistory = new ArrayList<Node>();
                resultHistory.add(key);
                findPath(child, resultHistory);
            }
        }
        counter++;
    }
    return allPaths;
}

public void findPath(String child, ArrayList<Node> resultHistory) {
    if (resultHistory.contains(paths.getNodeFromGraph(child))) {
        return;
    }
    resultHistory.add(paths.getNodeFromGraph(child));
    if(!(inList(resultHistory, allPaths))) {
        allPaths.add(resultHistory);
    }
    StringTokenizer st = new StringTokenizer(
        paths.getAdjacentString(paths.getNodeFromGraph(child)), ",");
    while (st.hasMoreTokens()) {
        child = st.nextToken();
        if (paths.getNodeFromGraph(child) != null) {
            findPath(child, resultHistory);
        }

    }
}

public boolean inList(ArrayList<Node> resultHistory,
    ArrayList<ArrayList<Node>> allPaths) {
    for (int i = 0; i < allPaths.size();i++) {
        if (allPaths.get(i).equals(resultHistory)) {
            return true;
        }
    }
    return false;
}

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

Может ли кто-нибудь предложить лучший способ выполнить это или сказать мне, что я сделал неправильно? Если алгоритмы верны, как лучше вывести все пути между двумя узлами?

РЕДАКТИРОВАТЬ: теперь я понимаю, что новые пути не создаются из дочерних узлов оригинала, как мне это сделать?


person Darkstarone    schedule 14.02.2013    source источник
comment
Почему? обычно вам это не нужно.   -  person AlexWien    schedule 15.02.2013
comment
DAG имитирует метаболический путь бактерии. Таким образом, ВСЕ пути могут представлять интерес для биологов, и способность находить странные пути, которые они не видели, потому что они состоят из 200 химических веществ, очень важна.   -  person Darkstarone    schedule 15.02.2013
comment
Почему бы вам просто не использовать модифицированный алгоритм BFS, где (i) у вас есть список ПУТЕЙ для посещения вместо вершины, и (ii) вы не проверяете, посещалась ли уже вершина во время поиска ?   -  person akappa    schedule 15.02.2013
comment
Будет ли это более эффективно? И если да, не могли бы вы уточнить ответ в псевдо- или java-коде? Таким образом, если это сработает, я могу проголосовать за вас и поставить оценку за ответ! :) Редактировать: я спрашиваю об эффективности, потому что у атм есть время загрузки 5-6 минут, чтобы найти около 3800 путей.   -  person Darkstarone    schedule 15.02.2013
comment
@akappa Я пытался изменить свою BFS, которую я использовал для кратчайших путей, но мне все время не хватает памяти. Код, поэтому я предполагаю, что делаю что-то не так, есть идеи?   -  person Darkstarone    schedule 15.02.2013
comment
Он оптимален, так как время его выполнения равно размеру ответа. Я дам вам реализацию псевдокода.   -  person akappa    schedule 15.02.2013


Ответы (3)


Здесь есть реализация, основанная на алгоритме BFS. Я обозначу путь как последовательность вершин l = (v, v', v'', ...) и выполню над ним следующие две операции:

  • extend(l, v): помещает вершину v в конец списка l;
  • v = back(l): получает последнюю вершину в списке l.

FindPaths(G, v) {
  // The first path is, simply, the starting node.
  // It should be the first vertex in topological order.
  pending_paths = {(v)};
  while (pending_paths is not empty) {
    l = pending_paths.remove_first(); // pop the first pending path
    output(l); // output it (or save it in a list to be returned, if you prefer)
    v = back(l); // Get the last vertex of the path
    foreach(edge (v, v') in G) { // For each edge outgoing from v'...
      // extend l with v' and put into the list of paths to be examined.
      pending_paths.push_back(extend(l, v'));
    } 
  }
}
person akappa    schedule 15.02.2013
comment
Спасибо, это поставило меня на правильный путь, я разместил свою собственную версию ниже, если это поможет кому-то в будущем! Ваше здоровье! - person Darkstarone; 19.02.2013
comment
Я рад, что это помогло вам в вашей работе :) - person akappa; 19.02.2013

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

AllPaths(currentNode):
  result = EmptyList()
  foreach child in children(node):
    subpaths = AllPaths(child)
    foreach subpath in subpaths:
      Append(result, currentNode + subpath)
  return result

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

person jacobm    schedule 15.02.2013
comment
Пытался использовать алгоритм, но получил stackoverflow на рекурсии, код здесь. Не уверен, что я сделал это неправильно, или количество путей слишком велико. - person Darkstarone; 18.02.2013
comment
Максимальная глубина рекурсии должна быть равна самому длинному пути в вашем графе. Знаете ли вы, какой самый длинный путь? Если это не очень долго, то у вас, вероятно, есть ошибка, из-за которой вы повторяетесь в случае, когда вы не должны. Если он достаточно длинный, вам может потребоваться вместо этого преобразовать рекурсивный алгоритм в явный алгоритм на основе стека (или переписать его на языке с лучшей поддержкой рекурсии, таком как Haskell, ML или Scheme). Если вам нужна помощь, зовите. - person jacobm; 18.02.2013
comment
Нет, я не знаю длину, но она легко может быть невероятно длинной. Вот почему я создаю программу - чтобы найти такие вещи! Не могли бы вы быстро взглянуть на код, который я разместил выше, чтобы убедиться, что моя логика верна? Если это так, мне, возможно, придется переключиться на Haskell! - person Darkstarone; 18.02.2013
comment
Но с точки зрения количества путей, netBeans дошел до 500 000 и разбился, используя алгоритм, который я опубликовал (измененный для проверки подпутей путей и добавления их в список), так что это вполне может быть проблемой глубины. - person Darkstarone; 18.02.2013
comment
Единственное, что я заметил, это то, что в строке 17 вы, вероятно, хотите продолжить вместо возврата, но вся эта проверка, вероятно, не нужна, поскольку этот алгоритм будет проходить каждый путь ровно один раз. (Кроме того, необходим ли inList? Не будет содержать работу?) Я предполагаю, что вы столкнулись с реальной проблемой глубины рекурсии. - person jacobm; 19.02.2013
comment
Ах спасибо, досадно надо. На карте могут быть одни и те же узлы, проходящие один и тот же путь через разные ребра (биология неэффективна), поэтому я не хочу иметь дело с огромным количеством повторений! Спасибо за вашу помощь, думаю, я пойду и посмотрю, справится ли Haskell с глубиной! - person Darkstarone; 19.02.2013

Итак, хотя псевдоним @akappa был хорошим началом, мне потребовалось некоторое время, чтобы понять, как заставить его работать, если кто-то еще наткнется на этот пост, вот как я это сделал:

public ArrayList<ArrayList<Node>> searchAll() {
    try {
        BufferedWriter out = new BufferedWriter(new FileWriter("output.txt"));
        //Gets Nodes from Hashmap and puts them into Queue
        for (Node key : paths.adjacencies.keySet()) {
            queue.addToQueue(new QueueItem(key.chemName, new ArrayList<Node>()));
        }
        while (queue.getSize() > 0) {
            QueueItem queueItem = queue.getFromQueue();
            Node node = paths.getNodeFromGraph(queueItem.getNodeId());
            if (node != null) {
                findRelationAll(node, queueItem, out);
            }
        }
        System.out.println("Cycle complete: Number of Edges: [" + resultHistoryAll.size() + "]");
        out.close();
    } catch (IOException e) {
    }
    return resultHistoryAll;

}

public void findRelationAll(Node node, QueueItem queueItem, BufferedWriter out) {
    if (!foundRelation) {
        StringTokenizer st = new StringTokenizer(paths.getAdjacentString(node), ",");
        while (st.hasMoreTokens()) {
            String child = st.nextToken();
            ArrayList<Node> history = new ArrayList<Node>();
            //Gets previous Nodes
            history.addAll(queueItem.getHistoryPath());
            //Makes sure path is unique
            if (history.contains(node)) {
                System.out.println("[" + counter2 + "]: Cyclic");
                counter2++;
                continue;
            }
            history.add(node);
            resultHistory = history;
            queue.addToQueue(new QueueItem(child, history));
            if (!(inList(resultHistory, resultHistoryAll))) {
                resultHistoryAll.add(resultHistory);
                try {
                    out.write("[" + counter + "]: " + resultHistory.toString());
                    out.newLine();
                    out.newLine();
                } catch (IOException e) {
                }
                System.out.println("[" + counter + "]: " + resultHistory.toString());
                counter++;
            } else {
                System.out.println("[" + counter3 + "]: InList");
                counter3++;
            }

        }
    }
}
//This checks path isn't in List already
public boolean inList(ArrayList<Node> resultHistory, ArrayList<ArrayList<Node>> allPaths) {
    for (int i = 0; i < allPaths.size(); i++) {
        if (allPaths.get(i).equals(resultHistory)) {
            return true;
        }
    }
    return false;
}

}

Приведенный выше код делает несколько дополнительных вещей, которые вам могут не понадобиться:

  • Он записывает пути в текстовый файл в виде списка узлов + значение счетчика.
  • Гарантирует, что путь не пересекает один и тот же узел дважды
  • Следит за тем, чтобы в окончательном списке не было двух одинаковых путей (в обычных обстоятельствах это ненужная работа)

Объект QueueItem — это просто способ хранения ранее посещенных узлов. Это часть кода nemanja, который мой код был основан на.

Совет ему, akappa (за наиболее эффективный ответ) и jacobm (за поиск решения, подобного моему исходному коду, и объяснение его ограничений).

В случае, если кто-то действительно заинтересован в работе; В настоящее время я обрабатываю более 5 миллионов путей, из которых 60 000 являются уникальными путями между 900 химическими веществами. И это всего лишь 1,2,3 или 4 химических пути... Биология сложная штука.

РЕДАКТИРОВАТЬ и Предупреждение: ЕСЛИ кто-то обрабатывает огромные объемы данных, как я, Windows 7 - или, по крайней мере, моя машина - бросает дерьмо и закрывает программу после того, как ArrayList > 63 000 объектов, независимо от того, как вы упорядочиваете указатели. Решение, с которого я начал, было после 60 000 объектов, перезапуск списка при добавлении всего в CSV. Это привело к некоторым дубликатам между итерациями списка, и в конечном итоге это должно быть превзойдено моим завтрашним переходом на Linux!

person Darkstarone    schedule 18.02.2013