web-dev-qa-db-ja.com

2つのグラフノード間のすべてのパスを見つける

ルートネットワーク上の相互接続されたノード間の最短パスを取得するために、ダイクストラアルゴリズムの実装に取り​​組んでいます。私は働きがあります。開始ノードをアルゴリズムに渡すと、すべてのノードへのすべての最短パスが返されます。

私の質問:Node Aからすべての可能なパスを取得してNode GまたはNode Aからすべての可能なパスを取得してNode A

59
Paul

all可能なパスを見つけることは、単純なパスが指数関数的に多数存在するため、難しい問題です。 k番目の最短経路[または最長経路]を見つけることでさえ NP-Hard です。

sからtまでのすべてのパス[または特定の長さまでのすべてのパス]を見つける1つの可能な解決策は、visitedセットを保持せずに BFS です、または加重バージョンの場合- 均一コスト検索 を使用できます

また、サイクルを持つすべてのグラフ[ DAG ではない]では、sからtの間に無限の数のパスが存在する可能性があることに注意してください。

49
amit

基本的に、あるノードから他のノードへのすべての可能なパスを見つけるバージョンを実装しましたが、可能な「サイクル」はカウントしません(使用しているグラフは循環的です)。したがって、基本的に、同じパス内に1つのノードが2回現れることはありません。グラフが非周期的である場合、2つのノード間のすべての可能なパスを見つけるように見えると言えると思います。それはうまく機能しているようで、グラフサイズが〜150の場合、マシン上でほぼ瞬時に実行されますが、実行時間は指数関数のようなものでなければならないので、グラフが大きくなります。

ここに、私が実装したものを示すJavaコードがあります。もっと効率的でエレガントな方法も必要だと思います。

Stack connectionPath = new Stack();
List<Stack> connectionPaths = new ArrayList<>();
// Push to connectionsPath the object that would be passed as the parameter 'node' into the method below
void findAllPaths(Object node, Object targetNode) {
    for (Object nextNode : nextNodes(node)) {
       if (nextNode.equals(targetNode)) {
           Stack temp = new Stack();
           for (Object node1 : connectionPath)
               temp.add(node1);
           connectionPaths.add(temp);
       } else if (!connectionPath.contains(nextNode)) {
           connectionPath.Push(nextNode);
           findAllPaths(nextNode, targetNode);
           connectionPath.pop();
        }
    }
}
10
Omer Hassan

実行可能な時間内でこれを行うことができないという科学的証拠の(多少わかりやすいですが)(少し小さい)バージョンを提供します。

私が証明するのは、任意のグラフsで選択された2つの異なるノード(たとえば、tG)間のすべての単純なパスを列挙する時間の複雑さが多項式ではないことです。これらのノード間のパスの量のみを考慮しているため、エッジコストは重要ではないことに注意してください。

グラフに適切に選択されたプロパティがある場合、これは簡単です。しかし、私は一般的なケースを検討しています。


stの間のすべての単純なパスをリストする多項式アルゴリズムがあるとします。

Gが接続されている場合、リストは空ではありません。 Gがそうでなく、stが異なるコンポーネントにある場合、それらの間のすべてのパスをリストするのは本当に簡単です。それらが同じコンポーネントにある場合、グラフ全体がそのコンポーネントのみで構成されているように見せることができます。 Gが実際に接続されていると仮定しましょう。

リストされたパスの数は多項式でなければなりません。そうでなければ、アルゴリズムはそれらをすべて返すことができませんでした。それらをすべて列挙する場合、最も長いものを提供する必要があるため、そこにあります。パスのリストがあるため、この最長パスである私を指すために簡単な手順を適用できます。

(まともな言い方を考えることはできませんが)この最長パスはGのすべての頂点を横断しなければならないことを示すことができます。したがって、多項式手順で ハミルトニアンパス を見つけました!しかし、これはよく知られたNP困難な問題です。

そして、私たちが考えていたこの多項式アルゴリズムは、 P = NP でない限り、存在する可能性は非常に低いと結論付けることができます。

9
araruna

ここ は、DFSの変更を使用して、sからtまでのすべてのパスを検索して出力するアルゴリズムです。また、動的プログラミングを使用して、可能なすべてのパスの数を見つけることができます。擬似コードは次のようになります。

AllPaths(G(V,E),s,t)
 C[1...n]    //array of integers for storing path count from 's' to i
 TopologicallySort(G(V,E))  //here suppose 's' is at i0 and 't' is at i1 index

  for i<-0 to n
      if i<i0
          C[i]<-0  //there is no path from vertex ordered on the left from 's' after the topological sort
      if i==i0
         C[i]<-1
      for j<-0 to Adj(i)
          C[i]<- C[i]+C[j]

 return C[i1]
4
yanis

find_paths [s、t、d、k]

この質問はもう少し古くなっています...しかし、私は帽子を指輪に投げ込みます。

私は個人的にfind_paths[s, t, d, k]の形式のアルゴリズムを見つけました。ここで:

  • sは開始ノードです
  • tはターゲットノードです
  • dは検索する最大の深さです
  • kは検索するパスの数です

dおよびkにプログラミング言語の無限の形式を使用すると、すべてのパスが得られます§。

§有向グラフを使用していて、stの間のすべてのundirectedパスが必要な場合は明らかに実行する必要がありますこれは両方の方法:

find_paths[s, t, d, k] <join> find_paths[t, s, d, k]

ヘルパー機能

個人的には再帰が好きですが、時には難しいこともありますが、とにかく最初にヘルパー関数を定義しましょう:

def find_paths_recursion(graph, current, goal, current_depth, max_depth, num_paths, current_path, paths_found)
  current_path.append(current)

  if current_depth > max_depth:
    return

  if current == goal:
    if len(paths_found) <= number_of_paths_to_find:
      paths_found.append(copy(current_path))

    current_path.pop()
    return

  else:
    for successor in graph[current]:
    self.find_paths_recursion(graph, successor, goal, current_depth + 1, max_depth, num_paths, current_path, paths_found)

  current_path.pop()

主な機能

これで、コア機能は簡単になります:

def find_paths[s, t, d, k]:
  paths_found = [] # PASSING THIS BY REFERENCE  
  find_paths_recursion(s, t, 0, d, k, [], paths_found)

まず、いくつかのことに注意してください。

  • 上記の擬似コードは言語のマッシュアップですが、pythonに非常によく似ています(コーディングしているため)。厳密なコピーと貼り付けは機能しません。
  • []は初期化されていないリストです。選択したプログラミング言語に相当するものに置き換えてください
  • paths_foundreferenceで渡されます。再帰関数が何も返さないことは明らかです。これを適切に処理してください。
  • ここでgraphは、何らかの形のhashed構造を想定しています。グラフを実装する方法はたくさんあります。いずれにしても、graph[vertex]directedグラフ内の隣接する頂点のリストを取得します-それに応じて調整します。
  • これは、「バックル」(自己ループ)、サイクル、マルチエッジを除去するために前処理を行っていることを前提としています
2
SumNeuron

あなたが望むのは、BFSに基づくFord–Fulkersonアルゴリズムの何らかの形だと思います。 2つのノード間のすべての拡張パスを見つけることにより、ネットワークの最大フローを計算するために使用されます。

http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm

1
richmb

実際に最短のパスから最長のパスへのパスの順序を気にする場合は、修正されたA *またはダイクストラアルゴリズムを使用することをお勧めします。わずかな変更を加えると、アルゴリズムは可能な限り多くのパスを最短パスから順に返します。したがって、本当に必要なものがすべて最短パスから最長パスに順序付けられている場合、これが道です。

最短から最長まで順序付けられたすべてのパスを返すことができるA *ベースの実装が必要な場合は、次の方法で実現できます。これにはいくつかの利点があります。まず、最短から最長へのソートが効率的です。また、必要な場合にのみ追加の各パスを計算するため、すべてのパスが必要ではないため早期に停止すると、処理時間を節約できます。また、次のパスを計算するたびに後続のパスでデータを再利用するため、より効率的です。最後に、必要なパスが見つかった場合、計算時間を節約して早期に中止できます。全体として、パスの長さによる並べ替えを気にする場合、これが最も効率的なアルゴリズムになるはずです。

import Java.util.*;

public class AstarSearch {
    private final Map<Integer, Set<Neighbor>> adjacency;
    private final int destination;

    private final NavigableSet<Step> pending = new TreeSet<>();

    public AstarSearch(Map<Integer, Set<Neighbor>> adjacency, int source, int destination) {
        this.adjacency = adjacency;
        this.destination = destination;

        this.pending.add(new Step(source, null, 0));
    }

    public List<Integer> nextShortestPath() {
        Step current = this.pending.pollFirst();
        while( current != null) {
            if( current.getId() == this.destination )
                return current.generatePath();
            for (Neighbor neighbor : this.adjacency.get(current.id)) {
                if(!current.seen(neighbor.getId())) {
                    final Step NeXTSTEP = new Step(neighbor.getId(), current, current.cost + neighbor.cost + predictCost(neighbor.id, this.destination));
                    this.pending.add(NeXTSTEP);
                }
            }
            current = this.pending.pollFirst();
        }
        return null;
    }

    protected int predictCost(int source, int destination) {
        return 0; //Behaves identical to Dijkstra's algorithm, override to make it A*
    }

    private static class Step implements Comparable<Step> {
        final int id;
        final Step parent;
        final int cost;

        public Step(int id, Step parent, int cost) {
            this.id = id;
            this.parent = parent;
            this.cost = cost;
        }

        public int getId() {
            return id;
        }

        public Step getParent() {
            return parent;
        }

        public int getCost() {
            return cost;
        }

        public boolean seen(int node) {
            if(this.id == node)
                return true;
            else if(parent == null)
                return false;
            else
                return this.parent.seen(node);
        }

        public List<Integer> generatePath() {
            final List<Integer> path;
            if(this.parent != null)
                path = this.parent.generatePath();
            else
                path = new ArrayList<>();
            path.add(this.id);
            return path;
        }

        @Override
        public int compareTo(Step step) {
            if(step == null)
                return 1;
            if( this.cost != step.cost)
                return Integer.compare(this.cost, step.cost);
            if( this.id != step.id )
                return Integer.compare(this.id, step.id);
            if( this.parent != null )
                this.parent.compareTo(step.parent);
            if(step.parent == null)
                return 0;
            return -1;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Step step = (Step) o;
            return id == step.id &&
                cost == step.cost &&
                Objects.equals(parent, step.parent);
        }

        @Override
        public int hashCode() {
            return Objects.hash(id, parent, cost);
        }
    }

   /*******************************************************
   *   Everything below here just sets up your adjacency  *
   *   It will just be helpful for you to be able to test *
   *   It isnt part of the actual A* search algorithm     *
   ********************************************************/

    private static class Neighbor {
        final int id;
        final int cost;

        public Neighbor(int id, int cost) {
            this.id = id;
            this.cost = cost;
        }

        public int getId() {
            return id;
        }

        public int getCost() {
            return cost;
        }
    }

    public static void main(String[] args) {
        final Map<Integer, Set<Neighbor>> adjacency = createAdjacency();
        final AstarSearch search = new AstarSearch(adjacency, 1, 4);
        System.out.println("printing all paths from shortest to longest...");
        List<Integer> path = search.nextShortestPath();
        while(path != null) {
            System.out.println(path);
            path = search.nextShortestPath();
        }
    }

    private static Map<Integer, Set<Neighbor>> createAdjacency() {
        final Map<Integer, Set<Neighbor>> adjacency = new HashMap<>();

        //This sets up the adjacencies. In this case all adjacencies have a cost of 1, but they dont need to.
        addAdjacency(adjacency, 1,2,1,5,1);         //{1 | 2,5}
        addAdjacency(adjacency, 2,1,1,3,1,4,1,5,1); //{2 | 1,3,4,5}
        addAdjacency(adjacency, 3,2,1,5,1);         //{3 | 2,5}
        addAdjacency(adjacency, 4,2,1);             //{4 | 2}
        addAdjacency(adjacency, 5,1,1,2,1,3,1);     //{5 | 1,2,3}

        return Collections.unmodifiableMap(adjacency);
    }

    private static void addAdjacency(Map<Integer, Set<Neighbor>> adjacency, int source, Integer... dests) {
        if( dests.length % 2 != 0)
            throw new IllegalArgumentException("dests must have an equal number of arguments, each pair is the id and cost for that traversal");

        final Set<Neighbor> destinations = new HashSet<>();
        for(int i = 0; i < dests.length; i+=2)
            destinations.add(new Neighbor(dests[i], dests[i+1]));
        adjacency.put(source, Collections.unmodifiableSet(destinations));
    }
}

上記のコードの出力は次のとおりです。

[1, 2, 4]
[1, 5, 2, 4]
[1, 5, 3, 2, 4]

nextShortestPath()を呼び出すたびに、オンデマンドで次に短いパスが生成されることに注意してください。必要な追加ステップのみを計算し、古いパスを2回走査しません。さらに、すべてのパスを必要とせず、実行を早期に終了すると判断した場合、計算時間を大幅に節約できます。必要なパスの数だけを計算し、それ以上は計算しません。

最後に、A *およびDijkstraアルゴリズムには多少の制限がありますが、それがあなたに影響するとは思わないことに注意してください。つまり、負の重みを持つグラフでは正しく機能しません。

ここにJDoodleへのリンクがあります。このリンクでは、ブラウザで自分でコードを実行し、動作を確認できます。グラフを変更して、他のグラフでも機能することを示すことができます。 http://jdoodle.com/a/ukx

非自明なグラフには指数関数的な数があるため、通常はそうしたくないでしょう。すべての(単純な)パス、またはすべての(単純な)サイクルを取得したい場合は、1つを見つけて(グラフをたどって)別のパスに戻ります。

1
jpalecek

「単純な」パスを見つけたいと思います(最初と最後のノードを除き、パスがノードに複数回表示されない場合、パスは単純です)。

問題はNP困難であるため、深さ優先検索の変形を行うことをお勧めします。

基本的に、Aから可能なすべてのパスを生成し、それらがGで終わるかどうかを確認します。

0
Zruty

あなたの質問に答えることができる素敵な記事があります/それらを収集する代わりにパスを印刷するだけです/オンラインIDEでC++/Pythonサンプルを試すことができることに注意してください。

http://www.geeksforgeeks.org/find-paths-given-source-destination/

0
Attila Karoly