图算法查找两个任意顶点之间的所有连接

时间:2008-09-12 04:36:52

标签: algorithm language-agnostic graph-theory

我正在尝试确定完成下述任务的最佳时间效率算法。

我有一组记录。对于这组记录,我有连接数据,表明该组中的记录对如何相互连接。这基本上代表了一个无向图,其中记录是顶点,连接数据是边缘。

集合中的所有记录都有连接信息(即不存在孤立记录;集合中的每个记录都连接到集合中的一个或多个其他记录。)

我想从集合中选择任意两条记录,并能够显示所选记录之间的所有简单路径。 “简单路径”是指在路径中没有重复记录的路径(即仅限于有限路径)。

注意:两个选择的记录总是不同的(即开始和结束顶点永远不会相同;没有循环)。

例如:

    If I have the following records:
        A, B, C, D, E

    and the following represents the connections: 
        (A,B),(A,C),(B,A),(B,D),(B,E),(B,F),(C,A),(C,E),
        (C,F),(D,B),(E,C),(E,F),(F,B),(F,C),(F,E)

        [where (A,B) means record A connects to record B]

如果我选择B作为我的起始记录而E作为我的结束记录,我想找到通过记录连接将记录B连接到记录E的所有简单路径。

   All paths connecting B to E:
      B->E
      B->F->E
      B->F->C->E
      B->A->C->E
      B->A->C->F->E

这是一个例子,实际上我可能有包含数十万条记录的集合。

16 个答案:

答案 0 :(得分:115)

看起来这可以通过深度优先搜索图来完成。 深度优先搜索将找到两个节点之间的所有非循环路径。此算法应该非常快并且可以扩展到大型图形(图形数据结构稀疏,因此它只使用尽可能多的内存需要)。

我注意到你上面指定的图只有一个方向边(B,E)。这是一个错字还是它真的是有向图?这个解决方案无论如何对不起,我无法用C做,我在那个区域有点弱。我希望您能够毫不费力地翻译这些Java代码。

<强> 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();
    }
}

节目输出:

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

答案 1 :(得分:23)

美国国家标准与技术研究院(NIST)在线算法和数据结构词典将此问题列为“all simple paths"并建议depth-first search。CLRS提供相关算法。

使用Petri网的聪明技术here

答案 2 :(得分:12)

这是我想出的伪代码。这不是任何特定的伪代码方言,但应该足够简单。

任何人都想分开这个。

  • [p]是表示当前路径的顶点列表。

  • [x]是符合条件的路径列表

  • [s]是源顶点

  • [d]是目的地顶点

  • [c]是当前的顶点(PathFind例程的参数)

假设有一种查找相邻顶点的有效方法(第6行)。

     1 PathList [p]
     2 ListOfPathLists [x]
     3 Vertex [s], [d]

     4 PathFind ( Vertex [c] )
     5     Add [c] to tail end of list [p]
     6     For each Vertex [v] adjacent to [c]
     7         If [v] is equal to [d] then
     8             Save list [p] in [x]
     9         Else If [v] is not in list [p]
    10             PathFind([v])
    11     Next For
    12     Remove tail from [p]
    13 Return

答案 3 :(得分:6)

由于this answer中给出的现有非递归DFS实现似乎已被破坏,所以让我提供一个实际可行的实现。

我是用Python编写的,因为我发现它非常易读且不受实现细节的影响(并且因为它有实用generators的方便yield关键字),但它应该相当简单移植到其他语言。

# 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

此代码维护两个并行堆栈:一个包含当前路径中的早期节点,另一个包含节点堆栈中每个节点的当前邻居索引(这样我们可以在弹出时继续遍历节点的邻居它从堆栈中退出)。我同样可以很好地使用单个堆栈(节点,索引)对,但我认为双栈方法更具可读性,并且可能更容易为其他语言的用户实现。

此代码还使用单独的visited集合,它始终包含当前节点和堆栈上的任何节点,以便让我有效地检查节点是否已经是当前路径的一部分。如果您的语言恰好具有“有序集”数据结构,该结构提供了高效的类似堆栈的推送/弹出操作高效的成员资格查询,您可以将其用于节点堆栈并摆脱单独的visited设置。

或者,如果您为节点使用自定义可变类/结构,则可以在每个节点中存储一个布尔标志,以指示它是否已作为当前搜索路径的一部分被访问。当然,如果您出于某种原因希望这样做,这种方法不允许您在同一图表上并行运行两次搜索。

以下是一些测试代码,演示了上面给出的函数是如何工作的:

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

在给定的示例图上运行此代码会产生以下输出:

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

请注意,虽然此示例图是无向的(即其所有边都是双向的),但该算法也适用于任意有向图。例如,删除C -> B边缘(通过从B的邻居列表中删除C)会产生除第三条路径(A -> C -> B -> D)之外的相同输出,这是否定的更长的时间。

Ps。很容易构建图形,像这样的简单搜索算法(以及此线程中给出的其他算法)的表现非常差。

例如,考虑在无向图上查找从A到B的所有路径的任务,其中起始节点A具有两个邻居:目标节点B(没有除A之外的其他邻居)和作为一部分的节点C <{3}}个 n +1个节点,如下所示:

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和B之间的唯一路径是直接路径,但是从节点A开始的天真DFS将浪费O( n !)时间无用地探索集团内的路径,即使(对人类而言)显而易见的是,这些路径都不可能导致B。

还可以构建具有类似属性的clique,例如通过让起始节点A连接目标节点B和另外两个节点C 1 和C 2 ,两者都连接到节点D 1 和D 2 ,两者都连接到E 1 和E 2 ,依此类推。对于像这样排列的 n 层节点,从A到B的所有路径的天真搜索将最终浪费O(2 n )时间在放弃之前检查所有可能的死角。

当然,从clique中的一个节点(C除外)或DAG的最后一层向目标节点B添加边缘,创建一个指数大的数字从A到B的可能路径,以及纯粹的局部搜索算法无法预先确定它是否会找到这样的边缘。因此,从某种意义上说,这种天真搜索的不良DAGs是由于他们缺乏对图表全局结构的认识。

虽然有各种预处理方法(例如迭代消除叶节点,搜索单节点顶点分隔符等),可以用来避免这些“指数时间死角”,我不知道可以在所有情况下消除它们的任何一般预处理技巧。一般的解决方案是在搜索的每个步骤检查目标节点是否仍然可以到达(使用子搜索),如果不是,则提前回溯 -​​ 但是,这会显着减慢搜索速度(最糟糕的是)对于许多包含此类病态死胡同的图表,与图表的大小成比例。

答案 4 :(得分:5)

与二楼相比,这是一个逻辑上更好看的递归版本。

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);
        }
    }
}
}

节目输出

B A C E 

B A C F E 

B E

B F C E

B F E 

答案 5 :(得分:4)

C代码中的解决方案。它基于使用最小内存的DFS。

#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);

}

答案 6 :(得分:1)

我最近解决了类似的问题,而不是所有解决方案,我只对最短的问题感兴趣。

我使用了'广度优先'迭代搜索,它使用状态队列',每个队列都包含一条记录,其中包含图表上的当前点以及到达那里的路径。

从队列中的单个记录开始,该记录具有起始节点和空路径。

每次迭代代码都会将项目从列表的头部开始,并检查它是否是一个解决方案(节点到达的是你想要的那个,如果是,我们就完成了),否则,它使用连接到当前节点的节点构造一个新的队列项,并修改基于前一个节点的路径的路径,并在末尾附加新的跳转。

现在,您可以使用类似的东西,但是当您找到解决方案时,不要停止,而是将该解决方案添加到“找到的列表”中并继续。

您需要跟踪已访问的节点列表,以便您永远不会回溯自己,否则您将无限循环。

如果你想要更多的伪代码发表评论或其他什么,我会详细说明。

答案 7 :(得分:1)

我认为你应该描述你背后的真正问题。我这样说是因为你要求时间有效,但问题的答案似乎成倍增长!

因此,我不会指望一种比指数更好的算法。

我会做回溯并浏览整个图表。为了避免循环,请沿途保存所有访问过的节点。当你返回时,取消标记节点。

使用递归:

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
}

或者那是错的?

编辑: 哦,我忘记了: 您应该通过利用该节点堆栈消除递归调用

答案 8 :(得分:1)

基本原则是您不必担心图形。这是标准问题,称为动态连接问题。有以下类型的方法可以实现节点的连接与否:

  1. 快速查找
  2. 快速联盟
  3. 改进算法(两者结合)
  4. 以下是我尝试过的C代码,时间复杂度最小O(log * n)这意味着对于65536个边缘列表,它需要4个搜索,而对于2 ^ 65536,它需要5次搜索。我正在通过算法分享我的实现:Algorithm Course from Princeton university

    提示:您可以从上面分享的链接中找到Java解决方案并提供正确的解释。

    /* 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;
    }
    

答案 9 :(得分:1)

这可能会迟到,但是这里使用堆栈的Java中DFS算法的相同C#版本来遍历两个节点之间的所有路径。随着递归,可读性更好。

    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();
        }
    }
This is a sample graph to test:

    // Sample graph. Numbers are edge ids
    //       1     3       
    //    A --- B --- C ----
    //    |     |  2       |
    //    | 4   ----- D    |
    //    ------------------

答案 10 :(得分:1)

find_paths [s,t,d,k]

这个问题很老,已经回答了。然而,没有一个显示可能是一个更灵活的算法来完成同样的事情。所以我会戴上帽子。

我个人发现find_paths[s, t, d, k]形式的算法很有用,其中:

  • s是起始节点
  • t是目标节点
  • d是要搜索的最大深度
  • k是要查找的路径数

使用您的编程语言的dk无限形式将为您提供所有路径§。

§很明显,如果您使用的是有向图,而您想要st之间的所有无向路径,则必须同时运行:

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_found引用传递。很明显,递归函数不会返回任何内容。妥善处理。
  • 此处graph假设某种形式的hashed结构。有很多方法可以实现图表。无论哪种方式,graph[vertex]都会为您提供定向图表中相邻顶点的列表 - 相应调整。
  • 这假设您已经预处理以删除“扣环”(自循环),循环和多边

答案 11 :(得分:0)

这是我头脑中的一个想法:

  1. 找到一个连接。 (深度优先搜索可能是一个很好的算法,因为路径长度并不重要。)
  2. 停用最后一段。
  3. 尝试在先前禁用的连接之前从最后一个节点找到另一个连接。
  4. 转到2,直到没有更多连接。

答案 12 :(得分:0)

据我所知,Ryan Fox(58343,Christian(58444)和你自己(58461)给出的解决方案一样好。我做的很好。在这种情况下,不要相信广度优先遍历会有所帮助,因为您不会获得所有路径。例如,边缘(A,B)(A,C)(B,C)(B,D)和{ {1}}您将获得路径(C,D)ABD,但不会获得ACD

答案 13 :(得分:0)

我找到了一种枚举所有路径的方法,包括包含循环的无限路径。

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

寻找原子路径&amp;循环

Definition

我们想要做的是找到从A点到B点的所有可能路径。由于涉及到周期,您不能只是通过并枚举它们。相反,你必须找到不循环的原子路径和尽可能小的循环(你不希望你的循环重复)。

我对原子路径的第一个定义是两次不经过同一节点的路径。但是,我发现这并不是一切可能性。在一些反思之后,我发现节点并不重要,但边缘是!因此,原子路径是两次不经过同一边缘的路径。

这个定义很方便,它也适用于循环:A点的原子循环是从A点到结束A点的原子路径。

<强>实施

Atomic Paths A -> B

为了获得从A点开始的所有路径,我们将从A点递归地遍历图形。在经历一个孩子时,我们将创建一个链接子 - &gt;父母为了知道我们已经越过的所有边缘。在我们去那个孩子之前,我们必须遍历那个链表并确保指定的边缘还没有走过。

当我们到达目的地时,我们可以存储我们找到的路径。

Freeing the list

要释放链接列表时出现问题。它基本上是以相反顺序链接的树。一个解决方案是将该列表双重链接,当找到所有原子路径时,从起​​点释放树。

但一个聪明的解决方案是使用引用计数(灵感来自垃圾收集)。每次向父项添加链接时,都会在其引用计数中添加一个链接。然后,当你到达一个路径的末尾时,你会向后移动,而引用计数等于1.如果它更高,你只需删除一个并停止。

Atomic Cycle A

寻找A的原子周期与寻找从A到A的原子路径相同。但是我们可以做几个优化。首先,当我们到达目的地点时,我们只想在边缘成本为负的情况下保存路径:我们只想经历吸收周期。

如前所述,在查找原子路径时会遍历整个图形。相反,我们可以将搜索区域限制为包含A的强连接组件。查找这些组件需要使用Tarjan算法对图形进行简单遍历。

组合原子路径和循环

此时,我们拥有从A到B的所有原子路径以及每个节点的所有原子循环,留给我们组织一切以获得最短路径。从现在开始,我们将研究如何在原子路径中找到原子循环的最佳组合。

答案 14 :(得分:0)

正如其他一些海报所描述的那样,简而言之,问题在于使用深度优先搜索算法递归搜索图表,以查找通信端节点之间路径的所有组合。

算法本身从您提供的起始节点开始,通过展开出现的搜索树的第一个子节点,逐步搜索直到找到目标节点,或者直到找到目标节点来检查其所有外出链接和进度。遇到没有子节点的节点。

搜索然后回溯,返回到尚未完成探索的最新节点。

blogged最近关于这个话题,在这个过程中发布了一个示例C ++实现。

答案 15 :(得分:0)

添加Casey Watson的答案,这是另一个Java实现。 使用起始节点初始化受访节点。

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