【问题标题】:Find all paths through an undirected, acyclic graph通过无向无环图查找所有路径
【发布时间】:2022-01-21 11:14:30
【问题描述】:

我正在尝试通过下图查找所有路径:

    start
    /   \
c--A-----b--d
    \   /
     end

它只是非循环的,因为具有小写名称的顶点只能被访问一次,例如顶点d 不应被访问,因为要将其包含在通过图的路径中,顶点b 必须被访问两次。

通过图表的所有允许路径是:

start,A,b,A,c,A,end
start,A,b,A,end
start,A,b,end
start,A,c,A,b,A,end
start,A,c,A,b,end
start,A,c,A,end
start,A,end
start,b,A,c,A,end
start,b,A,end
start,b,end

几乎我所有的谷歌搜索结果都是针对有向图的,而我能找到的让它们适用于我的无向图的唯一方法是将每条边添加两次,每个方向一次,我认为这比我现在的更混乱,无效的解决方案。

我想出的最好的方法是递归遍历图:

private HashSet<List<Edge>> paths = new();
private List<Edge> _path = new();
HashSet<Vertex> smallsVisited = new HashSet<Vertex>();
private void Traverse(Vertex start)
{
    var traverseEnd = GetVertexByName("end");
    if (start == traverseEnd)
    {
        paths.Add(_path.ToList());
        _path.Clear();
        return;
    }

    if (smallsVisited.Contains(start))
        return;

    if (start.Name != "start" && char.IsLower(start.Name[0]))
        smallsVisited.Add(start);

    var traverseStart = GetVertexByName("start");
    var neighbours = _adjacencyList[start].Where(v => v != traverseStart);
    foreach (var end in neighbours)
    {
        _path.Add(new Edge(start, end));
        Traverse(end);
    }
}

我调用Traverse 并使用名为start 的顶点来设置流程滚动。当参数start 是名为end 的顶点时,我希望遍历停止,但结果只是一个路径,即顶点start 到顶点b

我很新,只有几天,才能使用图表,并且对递归非常生疏。我做错了什么?

【问题讨论】:

  • 你是HashSet 访问的小写字母顶点只存在一次递归。你真的想在整个路径遍历中保留它。
  • @juharr 我刚刚注意到并解决了这个问题,我想,谢谢。

标签: c# recursion graph-theory


【解决方案1】:

以下是我如何处理递归遍历它。我已将图形设置为邻居列表的顶点名称字典。此外,由于“开始”以小写字符开头,我只是依靠它来让它不会通过那个顶点返回。这里的另一个技巧是将路径传递给当前顶点,以便您可以在每个递归中构建它,但每个递归都不会受到其他递归的影响。既然你有路径,你可以检查它是否包含小写顶点。另请注意,我们仍然可以根据图表获得无限路径和 StackOverflow。例如,如果您的图表将“b”更改为“B”,那么您可以在“A”和“B”之间无限次移动,而此代码不会以任何方式检测到这一点。

void Main()
{
    var graph = new Dictionary<string, List<string>>
    {
        ["start"] = new List<string>{"A", "b"},
        ["A"] = new List<string>{"c", "b", "end", "start"},
        ["b"] = new List<string>{"A", "d", "end", "start"},
        ["c"] = new List<string>{"A"},
        ["d"] = new List<string>{"b"},
        ["end"] = new List<string>{"A", "b"},
    };
    
    var result = Traverse(graph, "start", "end");

    foreach(var x in result)
    {
        Console.WriteLine(string.Join("-", x));
    }
}

public static IEnumerable<IEnumerable<string>> Traverse(
    Dictionary<string, List<string>> graph, 
    string current, 
    string end, 
    IEnumerable<string> path = null)
{
    // Initialize the path if we don't have one yet.
    path ??= Enumerable.Empty<string>();
    
    // Do not allow the path to go to a lower cast vertex 
    // more than once.
    if(char.IsLower(current[0]) && path.Contains(current))
    {
        yield break;
    }
    
    path = path.Append(current);
    
    // If we are at the end then return the path and break.
    if(current == end)
    {
        yield return path;
        yield break;
    }
    
    // Find all the paths from the current vertex through
    // each of it's neighbors and return them.
    foreach(var neighbor in graph[current])
    {
        foreach(var subPath in Traverse(graph, neighbor, end, path))
        {
            yield return subPath;
        }   
    }   
}

结果是

start-A-c-A-b-A-end
start-A-c-A-b-end
start-A-c-A-end
start-A-b-A-c-A-end
start-A-b-A-end
start-A-b-end
start-A-end
start-b-A-c-A-end
start-b-A-end
start-b-end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-30
    • 1970-01-01
    • 1970-01-01
    • 2014-01-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多