İki Rasgele Köşe Arasındaki Tüm Bağlantıları Bulmak İçin Grafik Algoritması


117

Aşağıda açıklanan görevi gerçekleştirmek için en iyi zaman verimli algoritmayı belirlemeye çalışıyorum.

Bir dizi kaydım var. Bu kayıt kümesi için, bu kümedeki kayıt çiftlerinin birbirine nasıl bağlandığını gösteren bağlantı verilerine sahibim. Bu, temelde, kayıtların köşeler ve bağlantı verilerinin kenarları olduğu, yönlendirilmemiş bir grafiği temsil eder.

Kümedeki tüm kayıtların bağlantı bilgileri vardır (ör. Öksüz kayıt yoktur; kümedeki her kayıt kümedeki bir veya daha fazla başka kayda bağlanır).

Kümeden herhangi iki kaydı seçmek ve seçilen kayıtlar arasındaki tüm basit yolları gösterebilmek istiyorum. "Basit yollar" ile, yolda yinelenen kayıtları olmayan yolları kastediyorum (yani yalnızca sonlu yollar).

Not: Seçilen iki kayıt her zaman farklı olacaktır (yani başlangıç ​​ve bitiş köşeleri asla aynı olmayacak; döngü yok).

Örneğin:

    Aşağıdaki kayıtlara sahipsem:
        A, B, C, D, E

    ve aşağıdakiler bağlantıları temsil eder: 
        (A, B), (A, C), (B-A), (B, D), (B, E), (B, F), (Cı-A), (C, E),
        (C, F), (D, B), (E, C), (E, E), (F, B), (K, C), (K, E)

        [burada (A, B), A kaydının B kaydına bağlandığı anlamına gelir]

Başlangıç ​​kaydım olarak B'yi ve bitiş kaydım olarak E'yi seçersem, B kaydını E'ye bağlayacak kayıt bağlantıları üzerinden tüm basit yolları bulmak isterim.

   B'yi E'ye bağlayan tüm yollar:
      B-> E
      B-> F-> E
      B> F-> C> D
      B-> A-> C-> E
      B-> A> C> F-> E

Bu bir örnektir, pratikte yüzbinlerce kayıt içeren setlerim olabilir.


Bağlantılara döngü denir ve bu cevapta sizin için birçok bilgi vardır.
elhoim

3
Lütfen sonlu bir döngüsüz bağlantılar listesi mi yoksa tüm olası döngülerle sonsuz bağlantı akışı mı istediğinizi belirtin. Krş Blorgbeard'ın cevabı.
Charles Stewart

kimse bu konuda yardımcı olabilir mi ??? stackoverflow.com/questions/32516706/…
tejas3006

Yanıtlar:


116

Görünüşe göre bu, grafiğin derinlemesine araştırılmasıyla gerçekleştirilebilir. Önce derinlik araması, iki düğüm arasındaki döngüsel olmayan tüm yolları bulacaktır. Bu algoritma çok hızlı olmalı ve büyük grafiklere ölçeklenmelidir (Grafik veri yapısı seyrek olduğundan, yalnızca ihtiyaç duyduğu kadar bellek kullanır).

Yukarıda belirttiğiniz grafiğin tek yönlü (B, E) kenarı olduğunu fark ettim. Bu bir yazım hatası mıydı yoksa gerçekten yönlendirilmiş bir grafik mi? Bu çözüm ne olursa olsun işe yarar. Üzgünüm C'de bunu yapamadım, o alanda biraz zayıfım. Yine de bu Java kodunu çok fazla sorun yaşamadan çevirebileceğinizi umuyorum.

Graph.java:

import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

public class Graph {
    private Map<String, LinkedHashSet<String>> map = new HashMap();

    public void addEdge(String node1, String node2) {
        LinkedHashSet<String> adjacent = map.get(node1);
        if(adjacent==null) {
            adjacent = new LinkedHashSet();
            map.put(node1, adjacent);
        }
        adjacent.add(node2);
    }

    public void addTwoWayVertex(String node1, String node2) {
        addEdge(node1, node2);
        addEdge(node2, node1);
    }

    public boolean isConnected(String node1, String node2) {
        Set adjacent = map.get(node1);
        if(adjacent==null) {
            return false;
        }
        return adjacent.contains(node2);
    }

    public LinkedList<String> adjacentNodes(String last) {
        LinkedHashSet<String> adjacent = map.get(last);
        if(adjacent==null) {
            return new LinkedList();
        }
        return new LinkedList<String>(adjacent);
    }
}

Search.java:

import java.util.LinkedList;

public class Search {

    private static final String START = "B";
    private static final String END = "E";

    public static void main(String[] args) {
        // this graph is directional
        Graph graph = new Graph();
        graph.addEdge("A", "B");
        graph.addEdge("A", "C");
        graph.addEdge("B", "A");
        graph.addEdge("B", "D");
        graph.addEdge("B", "E"); // this is the only one-way connection
        graph.addEdge("B", "F");
        graph.addEdge("C", "A");
        graph.addEdge("C", "E");
        graph.addEdge("C", "F");
        graph.addEdge("D", "B");
        graph.addEdge("E", "C");
        graph.addEdge("E", "F");
        graph.addEdge("F", "B");
        graph.addEdge("F", "C");
        graph.addEdge("F", "E");
        LinkedList<String> visited = new LinkedList();
        visited.add(START);
        new Search().depthFirst(graph, visited);
    }

    private void depthFirst(Graph graph, LinkedList<String> visited) {
        LinkedList<String> nodes = graph.adjacentNodes(visited.getLast());
        // examine adjacent nodes
        for (String node : nodes) {
            if (visited.contains(node)) {
                continue;
            }
            if (node.equals(END)) {
                visited.add(node);
                printPath(visited);
                visited.removeLast();
                break;
            }
        }
        for (String node : nodes) {
            if (visited.contains(node) || node.equals(END)) {
                continue;
            }
            visited.addLast(node);
            depthFirst(graph, visited);
            visited.removeLast();
        }
    }

    private void printPath(LinkedList<String> visited) {
        for (String node : visited) {
            System.out.print(node);
            System.out.print(" ");
        }
        System.out.println();
    }
}

Program Çıkışı:

B E 
B A C E 
B A C F E 
B F E 
B F C E 

5
Lütfen bunun genişliğe ilk geçiş olmadığını unutmayın. Genişlik ile ilk önce köke uzaklığı 0 olan tüm düğümleri, sonra uzaklığı 1, sonra 2 olanları vb.
Ziyaret edersiniz

14
Doğru, bu bir DFS. Bir BFS'nin, tüm N seviye düğümlerinden sonra işlenecek seviye- (N + 1) düğümleri sıraya koyan bir kuyruk kullanması gerekir . Ancak, OP'nin amaçları doğrultusunda, tercih edilen yollar sıralama düzeni belirtilmediğinden BFS veya DFS çalışacaktır.
Matt J

1
Casey, yıllardır bu soruna bir çözüm arıyordum. Yakın zamanda bu DFS'yi C ++ 'da uyguladım ve bir tedavi olarak çalışıyor.
AndyUK

6
Özyinelemenin dezavantajı, derin grafiğiniz olacaksa (A-> B-> C -> ...-> N), Java'da StackOverflowError'a sahip olabilmenizdir.
Rrr

1
Aşağıdaki C # 'da yinelemeli bir sürüm ekledim.
batta

23

Ulusal Standartlar ve Teknoloji Enstitüsü (NIST) çevrimiçi Algoritmalar ve Veri Yapıları Sözlüğü, bu sorunu " tüm basit yollar" olarak listeler ve derinlemesine bir arama önerir . CLRS, ilgili algoritmaları sağlar.

Petri Ağlarını kullanan akıllı bir teknik burada bulunur


2
Daha iyi bir çözüm için bana yardım edebilir misin? DFS'nin çalışması sonsuza kadar sürer : stackoverflow.com/q/8342101/632951
Pacerier

İki düğüm arasındaki tüm basit yollar kümesi küçük ve bulması kolay olsa da, DFS'nin çok verimsiz olduğu grafikler bulmanın kolay olduğunu unutmayın. Örneğin, başlangıç ​​düğümü A'nın iki komşusu olduğu yönsüz bir grafiği düşünün: hedef düğümü B (A'dan başka komşusu olmayan) ve tam olarak bağlı n + 1 düğüm kümesinin parçası olan bir düğüm C. A'dan B'ye açıkça tek bir basit yol olsa da, saf bir DFS , kliği keşfetmek için O ( n !) Zamanını boşa harcayacaktır . Benzer örnekler (bir çözüm, DFS üstel zaman alır) DAG'ler arasında da bulunabilir.
Ilmari Karonen

NIST diyor ki: "yolları olabilir bir derinlik ilk arama ile numaralandırılan."
chomp

13

İşte bulduğum sözde kod. Bu belirli bir sözde kod diyalekti değildir, ancak izlenecek kadar basit olmalıdır.

Bunu ayırmak isteyen herkes.

  • [p], geçerli yolu temsil eden tepe noktalarının bir listesidir.

  • [x], kriterleri karşılayan yolların bir listesidir

  • [s] kaynak köşe noktasıdır

  • [d] hedef köşedir

  • [c] mevcut tepe noktasıdır (PathFind rutini argümanı)

Bitişik köşelere bakmanın etkili bir yolu olduğunu varsayın (satır 6).

     1 Yol Listesi [p]
     2 ListOfPathLists [x]
     3 Köşe [ler], [d]

     4 PathFind (Köşe [c])
     5 [p] listesinin sonuna [c] ekleyin
     6 [c] 'ye bitişik her Köşe için [v]
     7 [v], [d] 'ye eşitse o zaman
     8 Listeyi [p] [x] olarak kaydedin
     9 Aksi takdirde [v] listede [p] değilse
    10 PathFind ([v])
    11 Sonraki
    12 [p] 'den kuyruğu çıkarın
    13 Dönüş

Lütfen 11. ve 12. adıma biraz ışık tutabilir misiniz
bozo kullanıcısı

Satır 11, sadece 6. satırda başlayan For döngüsü ile giden bitiş bloğunu gösterir. Satır 12, arayana dönmeden önce yol listesinin son elemanını kaldırmak anlamına gelir.
Robert Groves

PathFind'a yapılan ilk çağrı nedir - kaynak köşe (ler) ini iletiyor musunuz?
bozo kullanıcısı

Bu örnekte evet, ancak bu sözde kodla bire bir eşleştiren gerçek kod yazmak istemeyebileceğinizi unutmayın. İyi tasarlanmış bir koddan ziyade bir düşünce sürecini göstermek anlamına gelir.
Robert Groves

8

Bu yanıtta verilen mevcut yinelemeli olmayan DFS uygulaması bozuk göründüğü için, gerçekten çalışan bir tane sunmama izin verin.

Bunu Python'da yazdım, çünkü uygulama detaylarına göre oldukça okunaklı ve düzenli buluyorum (ve jeneratörleriyield uygulamak için kullanışlı bir anahtar kelimeye sahip olduğu için ), ancak diğer dillere aktarılması oldukça kolay olmalı.

# a generator function to find all simple paths between two nodes in a
# graph, represented as a dictionary that maps nodes to their neighbors
def find_simple_paths(graph, start, end):
    visited = set()
    visited.add(start)

    nodestack = list()
    indexstack = list()
    current = start
    i = 0

    while True:
        # get a list of the neighbors of the current node
        neighbors = graph[current]

        # find the next unvisited neighbor of this node, if any
        while i < len(neighbors) and neighbors[i] in visited: i += 1

        if i >= len(neighbors):
            # we've reached the last neighbor of this node, backtrack
            visited.remove(current)
            if len(nodestack) < 1: break  # can't backtrack, stop!
            current = nodestack.pop()
            i = indexstack.pop()
        elif neighbors[i] == end:
            # yay, we found the target node! let the caller process the path
            yield nodestack + [current, end]
            i += 1
        else:
            # push current node and index onto stacks, switch to neighbor
            nodestack.append(current)
            indexstack.append(i+1)
            visited.add(neighbors[i])
            current = neighbors[i]
            i = 0

Bu kod, iki paralel yığın tutar: biri geçerli yoldaki önceki düğümleri içerir, diğeri düğüm yığınındaki her düğüm için geçerli komşu dizinini içerir (böylece bir düğümün komşuları arasında yinelemeye devam edebiliriz. yığın). Tek bir (düğüm, dizin) çifti yığınını eşit derecede iyi kullanabilirdim, ancak iki yığın yönteminin daha okunabilir ve belki de diğer dilleri kullanan kullanıcılar için daha kolay uygulanabileceğini düşündüm.

Bu kod ayrıca, bir visiteddüğümün zaten geçerli yolun bir parçası olup olmadığını verimli bir şekilde kontrol etmeme izin vermek için her zaman geçerli düğümü ve yığındaki düğümleri içeren ayrı bir küme kullanır . Diliniz, hem verimli yığın benzeri push / pop işlemleri hem de verimli üyelik sorguları sağlayan "sıralı küme" veri yapısına sahipse , bunu düğüm yığını için kullanabilir ve ayrı visitedkümeden kurtulabilirsiniz .

Alternatif olarak, düğümleriniz için özel bir değiştirilebilir sınıf / yapı kullanıyorsanız, geçerli arama yolunun bir parçası olarak ziyaret edilip edilmediğini belirtmek için her düğümde bir boole bayrağı depolayabilirsiniz. Elbette, bu yöntem, herhangi bir nedenle bunu yapmak istemeniz durumunda, aynı grafik üzerinde paralel olarak iki arama yapmanıza izin vermez.

İşte yukarıda verilen fonksiyonun nasıl çalıştığını gösteren bazı test kodları:

# test graph:
#     ,---B---.
#     A   |   D
#     `---C---'
graph = {
    "A": ("B", "C"),
    "B": ("A", "C", "D"),
    "C": ("A", "B", "D"),
    "D": ("B", "C"),
}

# find paths from A to D
for path in find_simple_paths(graph, "A", "D"): print " -> ".join(path)

Bu kodu verilen örnek grafikte çalıştırmak aşağıdaki çıktıyı üretir:

A -> B -> C -> D
A -> B -> D
A -> C -> B -> D
A -> C -> D

Bu örnek grafiğin yönsüz olmasına rağmen (yani, tüm kenarları her iki yöne gitmektedir), algoritmanın rastgele yönlendirilmiş grafikler için de çalıştığını unutmayın. Örneğin, C -> Bkenarın kaldırılması ( Bkomşu listesinden kaldırılarak ), artık mümkün olmayan Cüçüncü yol ( A -> C -> B -> D) dışında aynı çıktıyı verir .


Ps. Bunun gibi basit arama algoritmalarının (ve bu başlıkta verilen diğerlerinin) çok zayıf performans gösterdiği grafikler oluşturmak kolaydır.

Örneğin, başlangıç ​​düğümü A'nın iki komşusu olduğu yönsüz bir grafikte A'dan B'ye tüm yolları bulma görevini düşünün: hedef düğüm B (A'dan başka komşusu olmayan) ve bir kliğin parçası olan bir C düğümü. ve n, böyle + 1 düğümleri:

graph = {
    "A": ("B", "C"),
    "B": ("A"),
    "C": ("A", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"),
    "D": ("C", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"),
    "E": ("C", "D", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"),
    "F": ("C", "D", "E", "G", "H", "I", "J", "K", "L", "M", "N", "O"),
    "G": ("C", "D", "E", "F", "H", "I", "J", "K", "L", "M", "N", "O"),
    "H": ("C", "D", "E", "F", "G", "I", "J", "K", "L", "M", "N", "O"),
    "I": ("C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "O"),
    "J": ("C", "D", "E", "F", "G", "H", "I", "K", "L", "M", "N", "O"),
    "K": ("C", "D", "E", "F", "G", "H", "I", "J", "L", "M", "N", "O"),
    "L": ("C", "D", "E", "F", "G", "H", "I", "J", "K", "M", "N", "O"),
    "M": ("C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "N", "O"),
    "N": ("C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "O"),
    "O": ("C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N"),
}

A ve B arasındaki tek yolun doğrudan yol olduğunu görmek kolaydır, ancak A düğümünden başlatılan saf bir DFS , klik içindeki yolları keşfetmek için O ( n !) Zamanını boşa harcayacaktır (bir insan için) bu yollardan hiçbiri muhtemelen B'ye götürmez.

Bir de gerçekleştirebilmesi DAG'leri başlangıç düğümü, A bağlantı hedef düğüm B ve diğer iki düğüm C olan örn benzer özelliklere sahip 1 ve C 2 düğümlerine bağlanmak her ikisi de, D 1 ve D, 2 E bağlanmak her ikisi de, 1 ve E 2 vb. Bunun gibi düzenlenmiş n düğüm katmanı için, A'dan B'ye tüm yollar için saf bir arama, pes etmeden önce tüm olası çıkmazları inceleyerek O (2 n ) zamanı boşa harcayacaktır .

Tabii ki, (C dışındaki) klik düğümlerin biri hedef düğüm B'ye bir kenar ekleme veya DAG son tabakadan, olur katlanarak büyük B etmek üzere olası yolların sayısı ve a oluşturmak tamamen yerel arama algoritması, böyle bir avantaj bulup bulmayacağını önceden söyleyemez. Dolayısıyla, bir anlamda, bu tür saf aramaların zayıf çıktı duyarlılığı , grafiğin küresel yapısına ilişkin farkındalık eksikliğinden kaynaklanmaktadır.

Bu "üstel zaman çıkmazlarından" bazılarından kaçınmak için kullanılabilecek çeşitli ön işleme yöntemleri (yaprak düğümlerini yinelemeli olarak ortadan kaldırmak, tek düğümlü köşe ayırıcıları aramak vb.) Varken, herhangi bir genel her durumda onları ortadan kaldırabilecek ön işleme hilesi Genel bir çözüm, aramanın her adımında hedef düğümün hala erişilebilir olup olmadığını kontrol etmek (bir alt arama kullanarak) ve değilse erken geri izlemek olabilir - ancak ne yazık ki bu, aramayı önemli ölçüde yavaşlatacaktır (en kötü durumda orantılı olarak çok grafikler için grafik boyutu) yok gibi patolojik çıkmaz içerir.


1
İşte aradığım şey bu, teşekkür ederim :)
arslan

Yinelemeli olmayan DFS çözümünüz için teşekkür ederiz. Son satır yazdırıldığında, sonucun sözdizimi hatası olduğuna dikkat edin, olması gerekir for path in find_simple_paths(graph, "A", "D"): print(" -> ".join(path)), printparantez eksikti.
David Oliván Ubieto

1
@ DavidOlivánUbieto: Python 2 kodu, bu yüzden parantez yok. :)
Ilmari Karonen

5

İşte ikinci kata kıyasla mantıksal olarak daha iyi görünen yinelemeli bir versiyon.

public class Search {

private static final String START = "B";
private static final String END = "E";

public static void main(String[] args) {
    // this graph is directional
    Graph graph = new Graph();
    graph.addEdge("A", "B");
    graph.addEdge("A", "C");
    graph.addEdge("B", "A");
    graph.addEdge("B", "D");
    graph.addEdge("B", "E"); // this is the only one-way connection
    graph.addEdge("B", "F");
    graph.addEdge("C", "A");
    graph.addEdge("C", "E");
    graph.addEdge("C", "F");
    graph.addEdge("D", "B");
    graph.addEdge("E", "C");
    graph.addEdge("E", "F");
    graph.addEdge("F", "B");
    graph.addEdge("F", "C");
    graph.addEdge("F", "E");
    List<ArrayList<String>> paths = new ArrayList<ArrayList<String>>();
    String currentNode = START;
    List<String> visited = new ArrayList<String>();
    visited.add(START);
    new Search().findAllPaths(graph, seen, paths, currentNode);
    for(ArrayList<String> path : paths){
        for (String node : path) {
            System.out.print(node);
            System.out.print(" ");
        }
        System.out.println();
    }   
}

private void findAllPaths(Graph graph, List<String> visited, List<ArrayList<String>> paths, String currentNode) {        
    if (currentNode.equals(END)) { 
        paths.add(new ArrayList(Arrays.asList(visited.toArray())));
        return;
    }
    else {
        LinkedList<String> nodes = graph.adjacentNodes(currentNode);    
        for (String node : nodes) {
            if (visited.contains(node)) {
                continue;
            } 
            List<String> temp = new ArrayList<String>();
            temp.addAll(visited);
            temp.add(node);          
            findAllPaths(graph, temp, paths, node);
        }
    }
}
}

Program Çıkışı

B A C E 

B A C F E 

B E

B F C E

B F E 

4

C kodunda çözüm. Minimum bellek kullanan DFS'ye dayanır.

#include <stdio.h>
#include <stdbool.h>

#define maxN    20  

struct  nodeLink
{

    char node1;
    char node2;

};

struct  stack
{   
    int sp;
    char    node[maxN];
};   

void    initStk(stk)
struct  stack   *stk;
{
    int i;
    for (i = 0; i < maxN; i++)
        stk->node[i] = ' ';
    stk->sp = -1;   
}

void    pushIn(stk, node)
struct  stack   *stk;
char    node;
{

    stk->sp++;
    stk->node[stk->sp] = node;

}    

void    popOutAll(stk)
struct  stack   *stk;
{

    char    node;
    int i, stkN = stk->sp;

    for (i = 0; i <= stkN; i++)
    {
        node = stk->node[i];
        if (i == 0)
            printf("src node : %c", node);
        else if (i == stkN)
            printf(" => %c : dst node.\n", node);
        else
            printf(" => %c ", node);
    }

}


/* Test whether the node already exists in the stack    */
bool    InStack(stk, InterN)
struct  stack   *stk;
char    InterN;
{

    int i, stkN = stk->sp;  /* 0-based  */
    bool    rtn = false;    

    for (i = 0; i <= stkN; i++)
    {
        if (stk->node[i] == InterN)
        {
            rtn = true;
            break;
        }
    }

    return     rtn;

}

char    otherNode(targetNode, lnkNode)
char    targetNode;
struct  nodeLink    *lnkNode;
{

    return  (lnkNode->node1 == targetNode) ? lnkNode->node2 : lnkNode->node1;

}

int entries = 8;
struct  nodeLink    topo[maxN]    =       
    {
        {'b', 'a'}, 
        {'b', 'e'}, 
        {'b', 'd'}, 
        {'f', 'b'}, 
        {'a', 'c'},
        {'c', 'f'}, 
        {'c', 'e'},
        {'f', 'e'},               
    };

char    srcNode = 'b', dstN = 'e';      

int reachTime;  

void    InterNode(interN, stk)
char    interN;
struct  stack   *stk;
{

    char    otherInterN;
    int i, numInterN = 0;
    static  int entryTime   =   0;

    entryTime++;

    for (i = 0; i < entries; i++)
    {

        if (topo[i].node1 != interN  && topo[i].node2 != interN) 
        {
            continue;   
        }

        otherInterN = otherNode(interN, &topo[i]);

        numInterN++;

        if (otherInterN == stk->node[stk->sp - 1])
        {
            continue;   
        }

        /*  Loop avoidance: abandon the route   */
        if (InStack(stk, otherInterN) == true)
        {
            continue;   
        }

        pushIn(stk, otherInterN);

        if (otherInterN == dstN)
        {
            popOutAll(stk);
            reachTime++;
            stk->sp --;   /*    back trace one node  */
            continue;
        }
        else
            InterNode(otherInterN, stk);

    }

        stk->sp --;

}


int    main()

{

    struct  stack   stk;

    initStk(&stk);
    pushIn(&stk, srcNode);  

    reachTime = 0;
    InterNode(srcNode, &stk);

    printf("\nNumber of all possible and unique routes = %d\n", reachTime);

}

2

Bu geç olabilir, ancak işte Casey'den bir yığın kullanarak iki düğüm arasındaki tüm yollar için geçiş yapmak için Java'daki DFS algoritmasının aynı C # sürümü. Okunabilirlik, her zaman olduğu gibi özyinelemeli ile daha iyidir.

    void DepthFirstIterative(T start, T endNode)
    {
        var visited = new LinkedList<T>();
        var stack = new Stack<T>();

        stack.Push(start);

        while (stack.Count != 0)
        {
            var current = stack.Pop();

            if (visited.Contains(current))
                continue;

            visited.AddLast(current);

            var neighbours = AdjacentNodes(current);

            foreach (var neighbour in neighbours)
            {
                if (visited.Contains(neighbour))
                    continue;

                if (neighbour.Equals(endNode))
                {
                    visited.AddLast(neighbour);
                    printPath(visited));
                    visited.RemoveLast();
                    break;
                }
            }

            bool isPushed = false;
            foreach (var neighbour in neighbours.Reverse())
            {
                if (neighbour.Equals(endNode) || visited.Contains(neighbour) || stack.Contains(neighbour))
                {
                    continue;
                }

                isPushed = true;
                stack.Push(neighbour);
            }

            if (!isPushed)
                visited.RemoveLast();
        }
    }
Bu, test edilecek örnek bir grafiktir:

    // Örnek grafik. Sayılar kenar kimlikleridir
    // 1 3       
    // A - B - C ---
    // | | 2 |
    // | 4 ----- D |
    // ------------------

1
mükemmel - özyinelemeyi yığın tabanlı yinelemeyle nasıl değiştirdiğiniz hakkında.
Siddhartha Ghosh

Hâlâ anlamıyorum, nedir neighbours.Reverse()? Öyle mi List<T>.Reverse ?

Bu yinelemeli olmayan versiyonu kontrol ettim, ancak doğru görünmüyor. özyinelemeli sürüm iyidir. belki yinelemeli olmayana değiştirildiğinde, küçük bir hata meydana geldi
arslan

@alim: Kabul edildi, bu kod basitçe kırıldı. (Geri izleme sırasında ziyaret edilen kümeden düğümleri doğru bir şekilde kaldırmıyor ve yığın işleme de karışık görünüyor. Düzeltilip düzeltilemeyeceğini görmeye çalıştım, ancak bu temelde tamamen yeniden yazmayı gerektirir.) doğru, çalışan özyinelemeli olmayan bir çözümle bir yanıt ekledi (Python'da, ancak diğer dillere taşımak nispeten kolay olmalı).
Ilmari Karonen

@llmari Karonen, Güzel, kontrol edeceğim, Harika iş.
arslan

1

Yakın zamanda buna benzer bir problemi çözdüm, sadece en kısa zamanda ilgilendiğim tüm çözümler yerine.

Her biri grafikte geçerli bir noktayı ve oraya ulaşmak için izlenen yolu içeren bir kaydı tutan bir durum kuyruğu kullanan 'enine' yinelemeli arama kullandım.

kuyrukta başlangıç ​​düğümü ve boş yolu olan tek bir kayıtla başlarsınız.

Kod üzerinden yapılan her yineleme, öğeyi listenin başından çıkarır ve bunun bir çözüm olup olmadığını kontrol eder (ulaşılan düğüm, istediğiniz düğümdür, eğer öyleyse, biz bitirdik), aksi takdirde yeni bir Geçerli düğüme bağlanan düğümlerle kuyruk öğesi ve sonda yeni atlama eklenerek önceki düğümün yolunu temel alan değiştirilmiş yollar.

Şimdi, benzer bir şey kullanabilirsiniz, ancak bir çözüm bulduğunuzda, durmak yerine, o çözümü 'bulunan listenize' ekleyin ve devam edin.

Ziyaret edilen düğümler listesini takip etmeniz gerekir, böylece asla kendi kendinize geri dönmezsiniz, aksi takdirde sonsuz bir döngüye sahip olursunuz.

Biraz daha sözde kod istiyorsanız, bir yorum veya başka bir şey gönderin, ayrıntılı olarak açıklayacağım.


6
Sadece en kısa yoldan ilgileniyorsanız, Dijkstra'nın Algoritmasının "çözüm" olduğuna inanıyorum :).
vicatcu

1

Bunun arkasındaki gerçek probleminizi tarif etmeniz gerektiğini düşünüyorum. Bunu söylüyorum çünkü zaman açısından verimli bir şey istiyorsunuz, ancak sorunun cevabı katlanarak artıyor gibi görünüyor!

Bu nedenle, üstel bir algoritmadan daha iyi bir algoritma beklemem.

Geriye dönüp tüm grafiği gözden geçirirdim. Döngüleri önlemek için, yol boyunca ziyaret edilen tüm düğümleri kaydedin. Geri döndüğünüzde, düğümün işaretini kaldırın.

Özyinelemeyi kullanma:

static bool[] visited;//all false
Stack<int> currentway; initialize empty

function findnodes(int nextnode)
{
if (nextnode==destnode)
{
  print currentway 
  return;
}
visited[nextnode]=true;
Push nextnode to the end of currentway.
for each node n accesible from nextnode:
  findnodes(n);
visited[nextnode]=false; 
pop from currenteay
}

Yoksa bu yanlış mı?

düzenleme: Ah, ve unuttum: Bu düğüm yığınını kullanarak özyinelemeli çağrıları ortadan kaldırmalısınız


Benim gerçek problemim tam olarak anlattığım gibi, sadece çok daha büyük setlerle. Bunun setin boyutu ile katlanarak büyüyeceğine katılıyorum.
Robert Groves

1

Temel ilke, grafikler için endişelenmenize gerek olmamasıdır.Bu, Dinamik bağlantı sorunu olarak bilinen standart bir sorundur. Düğümlerin bağlı olup olmadıklarını elde edebileceğiniz aşağıdaki yöntem türleri vardır:

  1. Hızlı arama
  2. Quick Union
  3. Geliştirilmiş Algoritma (Her ikisinin kombinasyonu)

İşte minimum zaman karmaşıklığı ile denediğim C Kodu O (log * n) Bu, 65536 kenar listesi için 4 arama ve 2 ^ 65536 için 5 arama gerektirdiği anlamına gelir. Algoritma uygulamamı paylaşıyorum: Princeton Üniversitesi'nden Algoritma Kursu

İPUCU: Java çözümünü yukarıda paylaşılan bağlantıdan uygun açıklamalarla bulabilirsiniz.

/* Checking Connection Between Two Edges */

#include<stdio.h>
#include<stdlib.h>
#define MAX 100

/*
  Data structure used

vertex[] - used to Store The vertices
size - No. of vertices
sz[] - size of child's
*/

/*Function Declaration */
void initalize(int *vertex, int *sz, int size);
int root(int *vertex, int i);
void add(int *vertex, int *sz, int p, int q);
int connected(int *vertex, int p, int q);

int main() //Main Function
{ 
char filename[50], ch, ch1[MAX];
int temp = 0, *vertex, first = 0, node1, node2, size = 0, *sz;
FILE *fp;


printf("Enter the filename - "); //Accept File Name
scanf("%s", filename);
fp = fopen(filename, "r");
if (fp == NULL)
{
    printf("File does not exist");
    exit(1);
}
while (1)
{
    if (first == 0) //getting no. of vertices
    {
        ch = getc(fp);
        if (temp == 0)
        {
            fseek(fp, -1, 1);
            fscanf(fp, "%s", &ch1);
            fseek(fp, 1, 1);
            temp = 1;
        }
        if (isdigit(ch))
        {
            size = atoi(ch1);
            vertex = (int*) malloc(size * sizeof(int));     //dynamically allocate size  
            sz = (int*) malloc(size * sizeof(int));
            initalize(vertex, sz, size);        //initialization of vertex[] and sz[]
        }
        if (ch == '\n')
        {
            first = 1;
            temp = 0;
        }
    }
    else
    {
        ch = fgetc(fp);
        if (isdigit(ch))
            temp = temp * 10 + (ch - 48);   //calculating value from ch
        else
        {
            /* Validating the file  */

            if (ch != ',' && ch != '\n' && ch != EOF)
            {
                printf("\n\nUnkwown Character Detected.. Exiting..!");

                exit(1);
            }
            if (ch == ',')
                node1 = temp;
            else
            {
                node2 = temp;
                printf("\n\n%d\t%d", node1, node2);
                if (node1 > node2)
                {
                    temp = node1;
                    node1 = node2;
                    node2 = temp;
                }

                /* Adding the input nodes */

                if (!connected(vertex, node1, node2))
                    add(vertex, sz, node1, node2);
            }
            temp = 0;
        }

        if (ch == EOF)
        {
            fclose(fp);
            break;
        }
    }
}

do
{
    printf("\n\n==== check if connected ===");
    printf("\nEnter First Vertex:");
    scanf("%d", &node1);
    printf("\nEnter Second Vertex:");
    scanf("%d", &node2);

    /* Validating The Input */

    if( node1 > size || node2 > size )
    {
        printf("\n\n Invalid Node Value..");
        break;
    }

    /* Checking the connectivity of nodes */

    if (connected(vertex, node1, node2))
        printf("Vertex %d and %d are Connected..!", node1, node2);
    else
        printf("Vertex %d and %d are Not Connected..!", node1, node2);


    printf("\n 0/1:  ");

    scanf("%d", &temp);

} while (temp != 0);

free((void*) vertex);
free((void*) sz);


return 0;
}

void initalize(int *vertex, int *sz, int size) //Initialization of graph
{
int i;
for (i = 0; i < size; i++)
{
    vertex[i] = i;
    sz[i] = 0;
}
}
int root(int *vertex, int i)    //obtaining the root
{
while (i != vertex[i])
{
    vertex[i] = vertex[vertex[i]];
    i = vertex[i];
}
return i;
}

/* Time Complexity for Add --> logn */
void add(int *vertex, int *sz, int p, int q) //Adding of node
{
int i, j;
i = root(vertex, p);
j = root(vertex, q);

/* Adding small subtree in large subtree  */

if (sz[i] < sz[j])
{
    vertex[i] = j;
    sz[j] += sz[i];
}
else
{
    vertex[j] = i;
    sz[i] += sz[j];
}

}

/* Time Complexity for Search -->lg* n */

int connected(int *vertex, int p, int q) //Checking of  connectivity of nodes
{
/* Checking if root is same  */

if (root(vertex, p) == root(vertex, q))
    return 1;

return 0;
}

Bu, sorulduğu gibi sorunu çözmüyor. OP, sadece bir yolun var olup olmadığını kontrol etmek değil, iki düğüm arasındaki tüm basit yolları bulmak ister.
Ilmari Karonen

1

bul_ yolları [s, t, d, k]

Bu soru eski ve şimdiden cevaplandı. Ancak hiçbiri aynı şeyi başarmak için daha esnek bir algoritma göstermiyor. Bu yüzden şapkamı ringe atacağım.

Kişisel olarak find_paths[s, t, d, k]yararlı bir form algoritması buluyorum , burada:

  • s başlangıç ​​düğümüdür
  • t hedef düğümdür
  • d, aranacak maksimum derinliktir
  • k bulunacak yolların sayısıdır

Programlama dilinizin sonsuz biçimini kullanmak için dve ksize tüm yolları§ verecektir.

Açıkçası, yönlendirilmiş bir grafik kullanıyorsanız ve aradaki tüm yönlendirilmemiş yolları istiyorsanız sve tbunu iki şekilde de çalıştırmanız gerekecektir:

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

Yardımcı İşlev

Kişisel olarak özyinelemeyi seviyorum, ancak bazen zor olabilmesine rağmen, yine de önce yardımcı fonksiyonumuzu tanımlayalım:

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()

Ana işlev

Bunun dışında, temel işlev önemsizdir:

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

İlk olarak, birkaç şeye dikkat edelim:

  • yukarıdaki sözde kod, dillerin bir karışımıdır - ancak en çok python'a benzemektedir (çünkü ben sadece içinde kod yazıyordum). Kesin bir kopyala-yapıştır çalışmayacaktır.
  • [] başlatılmamış bir listedir, bunu seçtiğiniz programlama dilinin eşdeğeriyle değiştirin
  • paths_foundreferansla geçilir . Özyineleme işlevinin hiçbir şey döndürmediği açıktır. Bunu uygun şekilde halledin.
  • burada graphbir tür hashedyapı varsayılıyor . Bir grafiği uygulamanın birçok yolu vardır. Her iki durumda da, graph[vertex]size yönlendirilmiş bir grafikte bitişik köşelerin bir listesini verir - buna göre ayarlayın.
  • bu, "tokaları" (kendi kendine döngüleri), döngüleri ve çoklu kenarları kaldırmak için önceden işlediğinizi varsayar

0

İşte aklıma gelen bir düşünce:

  1. Bir bağlantı bulun. (Önce derinlik araması muhtemelen bunun için iyi bir algoritmadır, çünkü yol uzunluğu önemli değildir.)
  2. Son segmenti devre dışı bırakın.
  3. Daha önce devre dışı bırakılan bağlantıdan önceki son düğümden başka bir bağlantı bulmaya çalışın.
  4. Başka bağlantı kalmayana kadar 2'ye gidin.

Bu genel olarak işe yaramayacaktır: Köşeler arasındaki iki veya daha fazla yolun aynı son kenara sahip olması oldukça olasıdır. Yönteminiz bu tür yollardan yalnızca birini bulacaktır.
Ilmari Karonen

0

Ryan Fox ( 58343 , Christian ( 58444 ) ve sizin ( 58461 ) tarafından verilen çözümler söyleyebildiğim kadarıyla , olabildiğince iyi. değil kenarları, Örneğin. bütün yolları olsun (A,B), (A,C), (B,C), (B,D)ve (C,D)yolları alacak ABDve ACDancak ABCD.


mweerden, Gönderdiğim en geniş geçiş, herhangi bir döngüden kaçınarak TÜM yolları bulacaktır. Belirttiğiniz grafik için, uygulama üç yolu da doğru bir şekilde bulur.
Casey Watson

Kodunuzu tam olarak okumadım ve enine ilk geçişi kullandığınızı varsaydım (çünkü öyle söylediniz). Ancak, yorumunuzdan sonra daha yakından incelendiğinde, aslında olmadığını fark ettim. Aslında Ryan, Christian ve Robert'ınki gibi hafızasız derinlemesine bir geçiş.
mweerden

0

Döngüler içeren sonsuz olanlar da dahil olmak üzere tüm yolları numaralandırmanın bir yolunu buldum.

http://blog.vjeux.com/2009/project/project-shortest-path.html

Atomik Yolları ve Döngüleri Bulmak

Definition

Yapmak istediğimiz şey, A noktasından B noktasına giden tüm olası yolları bulmaktır. İlgili döngüler olduğundan, bunların üzerinden geçip hepsini sıralayamazsınız. Bunun yerine, döngü yapmayan atomik yolu ve mümkün olan en küçük döngüleri bulmanız gerekecek (döngünüzün kendini tekrar etmesini istemezsiniz).

Atomik bir yolun ilk aldığım tanımı, aynı düğümden iki kez geçmeyen bir yoldur. Ancak bunun tüm olasılıkları almadığını öğrendim. Biraz düşündükten sonra, düğümlerin önemli olmadığını anladım, ancak kenarlar önemli! Yani atomik yol, aynı kenardan iki kez geçmeyen bir yoldur.

Bu tanım kullanışlıdır, aynı zamanda döngüler için de işe yarar: A noktasının atomik döngüsü, A noktasından A noktasına giden atomik bir yoldur.

uygulama

Atomic Paths A -> B

A noktasından başlayarak tüm yolu elde etmek için, grafiği A noktasından özyinelemeli olarak geçeceğiz. Bir çocuktan geçerken, tüm kenarları bilmek için bir çocuk -> ebeveyn yapacağız. çoktan geçti. O çocuğa gitmeden önce, bu bağlantılı listeden geçmeli ve belirtilen kenarın önceden geçilmediğinden emin olmalıyız.

Varış noktasına geldiğimizde bulduğumuz yolu saklayabiliriz.

Freeing the list

Bağlantılı listeyi serbest bırakmak istediğinizde bir sorun oluşur. Temelde ters sırada zincirlenmiş bir ağaçtır. Çözüm, bu listeyi çift bağlamak ve tüm atomik yollar bulunduğunda ağacı başlangıç ​​noktasından kurtarmak olabilir.

Ancak akıllı bir çözüm, bir referans sayımı kullanmaktır (Çöp Toplama'dan esinlenilmiştir). Bir ebeveyne her bağlantı eklediğinizde, referans sayısına bir tane eklersiniz. Daha sonra, bir yolun sonuna geldiğinizde, referans sayısı 1'e eşitken geriye doğru gidersiniz ve serbest kalırsınız. Daha yüksekse, birini kaldırır ve durursunuz.

Atomic Cycle A

A'nın atomik döngüsünü aramak, A'dan A'ya atomik yolu aramakla aynı şeydir. Ancak yapabileceğimiz birkaç optimizasyon var. İlk olarak, varış noktasına vardığımızda, yolu ancak kenarların toplamı negatifse kaydetmek istiyoruz: sadece soğurma döngülerinden geçmek istiyoruz.

Daha önce gördüğünüz gibi, atomik bir yol ararken tüm grafiğin üzerinden geçiliyor. Bunun yerine, arama alanını A içeren güçlü bir şekilde bağlantılı bileşenle sınırlayabiliriz. Bu bileşenleri bulmak, Tarjan algoritması ile grafiğin basit bir geçişini gerektirir.

Atomik Yolları ve Döngüleri Birleştirmek

Bu noktada, A'dan B'ye giden tüm atomik yollara ve her düğümün tüm atomik döngülerine sahibiz, en kısa yolu elde etmek için her şeyi organize etmek için bize bıraktık. Şu andan itibaren atomik bir yolda atomik döngülerin en iyi kombinasyonunu nasıl bulacağımızı inceleyeceğiz.


Bu, sorulan soruyu yanıtlamıyor gibi görünüyor.
Ilmari Karonen

0

Diğer posterlerin bazılarında ustalıkla açıklandığı gibi, özetle sorun, iletişim kuran uç düğümler arasındaki tüm yol kombinasyonları için grafiği yinelemeli olarak aramak için derinlik ilk arama algoritmasının kullanılmasıdır.

Algoritmanın kendisi, ona verdiğiniz başlangıç ​​düğümü ile başlar, tüm giden bağlantılarını inceler ve görünen arama ağacının ilk alt düğümünü genişleterek, bir hedef düğüm bulunana kadar veya bir düğümle karşılaşana kadar giderek daha derin ve daha derin arama yaparak ilerler. çocuğu olmayan.

Arama daha sonra geri döner ve henüz keşfetmeyi bitirmediği en son düğüme geri döner.

Bu konu hakkında oldukça yakın bir zamanda blog yazdım ve süreçte örnek bir C ++ uygulaması yayınladım.


0

Casey Watson'ın cevabına ek olarak, işte başka bir Java uygulaması. Ziyaret edilen düğümü başlangıç ​​düğümü ile başlatma.

private void getPaths(Graph graph, LinkedList<String> visitedNodes) {
                LinkedList<String> adjacent = graph.getAdjacent(visitedNodes.getLast());
                for(String node : adjacent){
                    if(visitedNodes.contains(node)){
                        continue;
                    }
                    if(node.equals(END)){
                        visitedNodes.add(node);
                        printPath(visitedNodes);
                        visitedNodes.removeLast();
                    }
                    visitedNodes.add(node);
                    getPaths(graph, visitedNodes);
                    visitedNodes.removeLast();  
                }
            }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.