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

时间:2020-03-05 18:52:01  来源:igfitidea点击:

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

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

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

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

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

例如:

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

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

解决方案

回答

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

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

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

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

现在,我们可以使用类似的方法,但是当我们找到一个解决方案而不是停止时,请将该解决方案添加到"找到的列表"中并继续。

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

如果我们想要更多的伪代码,请发表评论或者其他内容,我将进行详细说明。

回答

这是我脑海中浮现的一个念头:

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

回答

我认为我们应该在此背后描述真正问题。我之所以这样说,是因为我们要求的是省时的方法,但问题的答案似乎成倍增长!

因此,我不希望有比指数更好的算法。

我会回溯并遍历整个图表。为了避免循环,请一路上保存所有访问的节点。当我们返回时,请取消标记该节点。

使用递归:

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
}

还是那是错的?

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

回答

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

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

回答

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

任何人都想分开。

  • [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

回答

据我所知,瑞安·福克斯(58343,克里斯蒂安(58444)和你自己(58461)给出的解决方案都差不多,我不相信广度优先遍历在这种情况下会有所帮助不能获取所有路径,例如,具有边线((A,B)(A,C),((B,C)(B,D)(C,D)将获得路径" ABD"和" ACD",而不是" ABCD"。

回答

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

使用Petri Nets的巧妙技巧可在此处找到

回答

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

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时,我们将向后移动并释放。如果参考计数等于1,则只需移除一个并停止。

Atomic Cycle A

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

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

结合原子路径和循环

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