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


117

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

我有一套记录。对于这组记录,我具有连接数据,该数据指示该组记录中的记录对如何相互连接。这基本上表示一个无向图,其中记录是顶点,而连接数据是边。

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

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

注意:所选的两个记录将始终是不同的(即开始和结束顶点永远不会相同;没有周期)。

例如:

    如果我有以下记录:
        A,B,C,D,E

    并且以下表示连接: 
        (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)

        [其中(A,B)表示记录A连接到记录B]

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

   将B连接到E的所有路径:
      B→E
      B-> F-> E
      B-> F-> C-> E
      B-> A-> C-> E
      B-> A-> C-> F-> E

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


这些连接称为cycle这个答案为您提供了很多信息。
elhoim

3
请说出您是想要一个有限的无环连接列表,还是无限个具有所有可能循环的连接流。cf. Blorgbeard的答案。
查尔斯·斯图尔特2010年

有人能帮忙吗 ??? stackoverflow.com/questions/32516706/...
tejas3006

Answers:


116

似乎可以通过对图形进行深度优先搜索来完成。深度优先搜索将找到两个节点之间的所有非循环路径。该算法应该非常快并且可以扩展到大图(图数据结构稀疏,因此仅使用所需的内存)。

我注意到您在上面指定的图形只有一个方向(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 

5
请注意,这不是广度优先遍历。首先广度,首先访问到根的距离为0的所有节点,然后访问距离为1的节点,然后访问2,等等
。– mweerden

14
正确,这是DFS。BFS将需要使用队列,将第(N + 1)个节点排入队列,所有N个节点之后进行处理。但是,出于OP的目的,BFS或DFS都将起作用,因为未指定路径的首选排序顺序。
Matt J

1
凯西,多年来我一直在寻找解决这个问题的方法。我最近在C ++中实现了这个DFS,并且可以正常工作。
AndyUK

6
递归的缺点是,如果您有较深的图形(A-> B-> C-> ...-> N),则Java中可能会出现StackOverflowError。
Rrr 2012年

1
我在下面的C#中添加了迭代版本。
batta 2014年

23

美国国家标准技术研究院(NIST)在线算法和数据结构词典将该问题列为“ 所有简单路径”,并建议进行深度优先搜索。CLRS提供了相关的算法。

这里可以找到使用Petri网的巧妙技巧


2
您能帮我提供更好的解决方案吗?DFS 永远需要运行:stackoverflow.com/q/8342101/632951
Pacerier,2011年

请注意,即使两个节点之间的所有简单路径的集合很小并且很容易找到,对于DFS效率很低的图也很容易提出。例如,考虑一个无向图,其中起始节点A有两个邻居:目标节点B(除A之外没有其他邻居),以及节点C,它是n + 1个节点的完全连接集团的一部分。即使显然只有一条从A到B的简单路径,但幼稚的DFS会浪费O(n!)时间,无益地探索集团。在DAG中也可以找到类似的示例(一种解决方案,DFS花费指数时间)。
Ilmari Karonen '16

NIST说:“ 可以通过深度优先搜索枚举路径。”

13

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

任何人都想分开。

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

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

  • [s]是源顶点

  • [d]是目标顶点

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

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

     1个路径列表[p]
     2 ListOfPathLists [x]
     3个顶点[s],[d]

     4 PathFind(顶点[c])
     5将[c]添加到列表[p]的末尾
     6对于与[c]相邻的每个顶点[v]
     7如果[v]等于[d],则
     8在[x]中保存列表[p]
     9其他如果[v]不在列表[p]中
    10 PathFind([v])
    11下一个
    12从[p]上去除尾巴
    13返回

能否请你一些线索步骤11和12
博若用户

第11行只是表示与For循环一起从第6行开始的结束块。第12行意味着在返回调用者之前删除路径列表的最后一个元素。
罗伯特·格罗夫斯

最初对PathFind的调用是什么-您传递源顶点[s]吗?
bozo用户

在此示例中,是,但请记住,您可能不想编写使用该伪代码一对一映射的真实代码。它的意思更多是为了说明思想过程,而不是精心设计的代码。
罗伯特·格罗夫斯

8

由于此答案中给出的现有非递归DFS实现似乎已失效,因此让我提供一个实际可行的方法。

我已经用Python编写了此代码,因为我发现它非常易读且不受实现细节的干扰(并且因为它具有yield用于实现generators的方便关键字),但是移植到其他语言应该相当容易。

# 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)不再可用。


附言 构造图很容易,对于这种图,简单的搜索算法(例如该线程(以及该线程中给出的其他算法))执行效果非常差。

例如,考虑在无向图上查找从A到B的所有路径的任务,其中起始节点A有两个邻居:目标节点B(除A之外没有其他邻居)和节点C(属于集团)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。

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

当然,从集团中的一个节点(不是C)或从DAG的最后一层向目标节点B添加一条边,将会创建从A到B的大量可能路径,并且纯粹的本地搜索算法无法真正提前告知它是否会找到这样的边缘。因此,从某种意义上说,这种天真的搜索对输出的敏感性很差,这是由于它们缺乏对图形全局结构的了解。

尽管可以使用多种预处理方法(例如迭代地消除叶节点,搜索单节点顶点分隔符等)来避免这些“指数时间死胡同”,但我不知道任何一般方法可以在所有情况下消除它们的预处理技巧。通用的解决方案是在搜索的每个步骤中检查目标节点是否仍可访问(使用子搜索),如果无法访问则尽早回溯,但是but,这会大大降低搜索速度(最坏的情况是,与图的大小成比例),对于许多包含此类病理死角的图。


1
那就是我想要的,谢谢:)
arslan '16

感谢您的DFS非递归解决方案。只需注意打印结果的最后一行有语法错误,应该是for path in find_simple_paths(graph, "A", "D"): print(" -> ".join(path))print缺少括号。
DavidOlivánUbieto,18年

1
@DavidOlivánUbieto:这是Python 2代码,这就是为什么没有括号的原因。:)
Ilmari Karonen

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 

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

}

2

这可能已经晚了,但是这是Java中从Casey到CFS的C#版本的DFS算法,它使用堆栈遍历两个节点之间的所有路径。与往常一样,递归的可读性更好。

    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();
        }
    }
这是要测试的样本图:

    //示例图。数字是边缘ID
    // 1 3       
    // A --- B --- C ----
    // | | 2 |
    // | 4 ----- D |
    // ------------------

1
很棒-关于如何用基于堆栈的迭代替换递归。
Siddhartha Ghosh 2015年

我还是不明白,那是neighbours.Reverse()什么?是List<T>.Reverse

我检查了此非递归版本,但似乎不正确。递归版本就可以了。也许当更改为非递归时,会发生一个小错误
arslan '02

@alim:同意,此代码已被破坏。(回溯时,它不能正确地从访问集中删除节点,并且堆栈处理似乎也很混乱。我试图查看它是否可以修复,但这基本上需要完全重写。)添加一个正确的,有效的非递归解决方案的答案(在Python中,但移植到其他语言应该相对容易)。
Ilmari Karonen '16

@llmari Karonen,尼斯,我要检查,做得好。
arslan '16

1

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

我使用了“先行先广”的迭代搜索,该搜索使用状态队列。每个迭代都保存一条记录,其中包含图形上的当前点以及到达该点的路径。

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

通过代码的每次迭代都会使该项目脱离列表的开头,并检查它是否是一种解决方案(到达的节点是您想要的节点,如果可以的话,我们就完成了),否则,它将构造一个新的节点。队列项,其中节点连接到当前节点,并且修改的路径基于上一个节点的路径,并在末尾附加新的跳转。

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

您需要跟踪已访问的节点列表,这样就永远不会回溯自己,否则就会陷入无限循环。

如果您想要更多的伪代码,请发表评论或其他内容,我会详细说明。


6
我相信,如果您只对最短的路径感兴趣,那么Dijkstra的算法就是“解决方案” :)。
vicatcu,2010年

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
}

还是那是错的?

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


我真正的问题正像我所描述的,只是有更大的集合。我同意这似乎与场景的大小成指数增长。
罗伯特·格罗夫斯

1

基本原理是您不必担心图形。这是标准问题,称为动态连接问题。您可以通过以下几种方法来实现是否连接节点:

  1. 快速查找
  2. 快速联盟
  3. 改进算法(两者结合)

这是我用最小时间复杂度O(log * n)尝试过的C代码,这意味着对于65536个边列表,它需要4个搜索,而对于2 ^ 65536,它需要5个搜索。我正在分享算法的实现:普林斯顿大学的算法课程

提示:您可以从上面共享的链接中找到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;
}

这似乎不能解决所要求的问题。OP希望找到两个节点之间的所有简单路径,而不仅仅是检查路径是否存在。
Ilmari Karonen '16

1

find_paths [s,t,d,k]

这个问题已经老了,已经回答了。但是,没有人显示出完成同一件事的更灵活的算法。所以我将帽子戴上戒指。

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

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

用无穷大的编程语言的形式d,并k会给你所有paths§。

§显然,如果您使用的是有向图,并且希望它们之间所有无方向的路径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]都可以在有图中获取相邻顶点的列表-进行相应调整。
  • 这假定您已进行了预处理以去除“带扣”(自环),循环和多边缘

0

这是我脑海中浮现的想法:

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

通常这将不起作用:顶点之间的两条或更多条路径具有相同的最后边缘是很有可能的。您的方法只能找到这样的路径之一。
Ilmari Karonen '16

0

据我所知,瑞安·福克斯(58343,克里斯蒂安(58444)和你自己(58461)给出的解决方案都差不多,我不相信广度优先遍历在这种情况下会有所帮助无法获得全部的路径。例如,对于边缘(A,B)(A,C)(B,C)(B,D)(C,D)你将得到的路径ABDACD,但不会ABCD


mweerden,我提交的广度优先遍历将找到所有路径,同时避免任何循环。对于您指定的图,实现正确找到所有三个路径。
凯西·沃森

我没有完全阅读您的代码,并假设您使用了广度优先遍历(因为您这样说)。但是,在您发表评论后仔细检查时,我发现事实并非如此。像Ryan,Christian和Robert一样,它实际上是无记忆的深度优先遍历。
mweerden

0

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

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

寻找原子路径和周期

Definition

我们想要做的是找到从A点到B点的所有可能路径。由于涉及到循环,因此您不能仅遍历所有循环。相反,您将必须找到不会循环的原子路径和尽可能小的循环(您不希望循环重复自己)。

我对原子路径的第一个定义是不会两次通过同一节点的路径。但是,我发现并没有采取所有可能。经过一番反思,我发现节点并不重要,但是边缘很重要!因此,原子路径是不会两次通过同一边的路径。

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

实作

Atomic Paths A -> B

为了获得从点A开始的所有路径,我们将从点A递归遍历该图。在通过一个子节点时,我们将使一个子节点->父节点链接,以便知道我们所有的边已经越过。在转到那个孩子之前,我们必须遍历该链表,并确保尚未遍历指定的边。

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

Freeing the list

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

但是,一个聪明的解决方案是使用引用计数(由Garbage Collection启发)。每次您添加到父项的链接时,都会在其引用计数中添加一个。然后,当您到达路径的末尾时,您将向后移动并释放,而引用计数等于1。如果引用计数更高,则只需删除一个并停止。

Atomic Cycle A

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

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

结合原子路径和周期

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


这似乎无法回答所问的问题。
Ilmari Karonen '16

0

正如其他一些发帖人所恰当描述的那样,总的来说,问题在于使用深度优先搜索算法来递归地搜索图形以查找通信端节点之间路径的所有组合。

该算法本身从您提供的起始节点开始,检查其所有传出链接,并通过扩展出现的搜索树的第一个子节点,进行越来越深的搜索,直到找到目标节点或遇到一个节点为止,进行搜索那没有孩子。

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

我最近在博客中发布了有关此主题的文章,并在此过程中发布了示例C ++实现。


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();  
                }
            }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.