【问题标题】:DFS Graph Recording Path (Pathfinding)DFS图记录路径(寻路)
【发布时间】:2012-11-27 02:16:21
【问题描述】:

我已经实现了一个简单的 DFS(非递归),它“测试”StartNode 和 EndNode 之间的路径是否存在。它按预期工作(处理双向/有向图) - 但我只是无法弄清楚如何存储路径以供以后使用。

目前我正在调试打印访问的节点,但这不是应该存储的内容。

有人可以帮我/解释一下 - 我到底应该存储什么以及在什么时候将节点列表从 NodeStart 返回到 NodeEnd?

这是示例图表:

这里是DFS遍历函数:

bool DFS(CNavigationGraph *pNavGraph, CNavigationGraphNode* pFromNode, CNavigationGraphNode* pToNode)
{
    assert(pNavGraph);
    assert(pFromNode);
    assert(pToNode);

    std::vector<CNavigationGraphNode*> vpVisitedNodes;
    std::vector<CNavigationGraphNode*> stack;

    stack.push_back(pFromNode);

    while(!stack.empty())
    {
        CNavigationGraphNode     *pTop = stack.back();
        stack.pop_back();

        // Ok We've reached pToNode - means, path pFromNode to pToNode available
        if(pTop == pToNode)
        {
            for(int a = 0; a < vpVisitedNodes.size(); a++)
            {
                CLogger::Instance()->Write(XLOGEVENT_LOCATION, "{VISITED} %s",vpVisitedNodes[a]->GetNodeName().data());
            }
            return true;
        }

        // Add to visited list
        vpVisitedNodes.push_back(pTop);

        // Look for adjacent Nodes for pTop
        std::vector<CNavigationGraphNode*> vpAdjacentNodes;
        pNavGraph->GetAdjacentNodes(pTop->GetNodeName(), vpAdjacentNodes);
        for(int x = 0; x < vpAdjacentNodes.size(); x++)
        {
            // Add to stack if not visited
            if(IsVisited(vpVisitedNodes, vpAdjacentNodes[x]) == false)
                stack.push_back(vpAdjacentNodes[x]);
        }
    }

    // Path not found
    return false;
}

这是调试输出:

在 Node1 和 Node3 之间查找路径

<main> [] DFS TRAVERSE TEST (DIRECTIONAL)
<DFS> [] {VISITED} Node1
<DFS> [] {VISITED} Node4
<DFS> [] {VISITED} Node5
<main> [] Path from Node1 to Node3 - YES

查找节点 3 和节点 1 之间的路径

<main> [] DFS TRAVERSE TEST (DIRECTIONAL)
<main> [] Path from Node3 to Node1 - NO

【问题讨论】:

    标签: c++ graph path-finding


    【解决方案1】:

    如果我正确理解了您的算法(并且它是 DFS),
    从起点开始,您将向第一个未访问节点的方向迈出一步。如果没有从该节点到您的目标的路线,则您退后一步并尝试前往下一个未访问过的节点,同时仔细管理访问过的节点。

    您只需要添加一个堆栈,您始终将要向其推送的节点推送到该堆栈中, 如果您不得不退后一步,则将其从堆栈中弹出。 此堆栈将存储您从 start_node 到目标的路线。它还可以帮助您确定退后的位置。

    这是你的代码,最后它比我想象的要长一点,但它是:

    // I call fromNode: start_node, toNode: target_node.
    
    std::stack<CNavigationGraphNode*> DFS(CNavigationGraph *pNavGraph, CNavigationGraphNode* start_node, CNavigationGraphNode* target_node)
    {
        using namespace std;
    
        stack<CNavigationGraphNode*> route; // route to target
        unordered_set<CNavigationGraphNode*> visited_nodes;  // hash set on who is visited
        vector<CNavigationGraphNode*> adjacent_nodes;
    
        CNavigationGraphNode* current_node = start_node;
        while(current_node!=target_node)
        {
          pNavGraph->GetAdjacentNodes(current_node->GetNodeName(), adjacent_nodes); 
    
          // "for each"; you can use any form of looping through the neighbors of current_node.
          bool found_non_visited = false;
          for(auto node : adjacent_nodes) 
          {
            if(visited_nodes.find(node) == visited_nodes.end())
            {
              route.push(current_node);
              visited_nodes.insert(node);
              current_node = node;
              found_non_visited = true;
              break;
            }
          }
          if(found_non_visited) 
            continue;
    
          if(route.empty())
            return route; // empty route means no route found
          current_node = route.pop();
        }
        route.push(target);
        return route;
    }
    

    现在您可以pop_back 从起点到目标,或pop 从目标到起点。如果路由为空,则对应于从原始函数返回 false。

    Reference on unordered_set 和 stack,都在 STL 中。它是用 bucket has 实现的,因此它比 set 或 map 快得多,后者通常用红黑树实现。

    备注:std::unordered_set 是 C++11 扩展,如果您使用 C++03,请随意替换为较慢的 std::set。

    【讨论】:

      【解决方案2】:

      好吧,您可以使用parent map(map:Vertex->Vertex)来代替visited set。

      在遍历图形时修改映射,这样如果您从节点 u 发现节点 v,则添加 parent[v] = u。初始化parent[source] = NULL。

      现在,您所要做的就是迭代(伪代码):

      current <- dest
      while current != NULL:
         print current
         current <- parent[current]
      

      它会以相反的顺序为您提供路径。如果要实现路径的原始顺序,可以存储到堆栈(而不是 print 语句)并迭代堆栈。

      这与线程中为 BFS 解释的想法非常相似:How can I find the actual path found by BFS?

      【讨论】:

      • 使用堆栈比使用父地图便宜得多。
      • @BarnabasSzabolcs:也许,但是:(1) 优势很小,因为无论如何你都保持visited 设置。我不确定visited set + stack 是否应该比parent map 更有效。 (2) 使用parent 映射比堆栈有一些非常重要的优势,主要是健壮性。这种方法非常适合所有图发现算法(如 BFS),并且让您几乎不费吹灰之力就可以将实现从 DFS 更改为 BFS,这是一个重要方面。
      • 第一个参数的观点,我同意 BFS 需要父地图(如果你在找到路线后返回,你可能不想返回它)。
        关于第二个论点:堆栈不健壮是什么意思?
      • 我建议您考虑使用std::unordered_map。它的复杂度为 O(1),而 std::map 在搜索/插入时的复杂度为 O(log n)。
      • @BarnabasSzabolcs:我的意思是,通过简单地将集合从堆栈更改为队列,可以轻松地将通用循环 + 集合遍历从 DFS 更改为 BFS。但是,如果您想获取实际路径,堆栈解决方案不适合 BFS,而地图则适合。希望我足够清楚......如果没有,请打开聊天,我会尝试详细说明。
      猜你喜欢
      • 1970-01-01
      • 2019-07-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多