【问题标题】:Calling a custom function on each node during DFS traversal在 DFS 遍历期间在每个节点上调用自定义函数
【发布时间】:2021-11-12 02:07:47
【问题描述】:

我想知道编写 DFS 遍历的最优雅的方法是什么,可以适应解决不同的问题(在 C++ 中)。

我正在考虑将函数指针和void * 传递给我的函数,并让用户传递将在每个节点上使用的回调。

这就是我所拥有的:

traversals.hpp

typedef std::shared_ptr<struct Node> NodePtr;
typedef std::vector<NodePtr> NodeVector;

struct Node {
    int id{0};
    NodeVector children;
};

bool NodeIsInVector(NodePtr node, NodeVector node_vector);

void DFS(NodePtr node, void (*callback)(NodePtr, void * userdata)=nullptr, void * userdata=nullptr);

traversals.cpp

bool NodeIsInVector(NodePtr node, NodeVector node_vector){
    auto result = std::find(node_vector.begin(), node_vector.end(), node);
    return result != node_vector.end();
}

void DFS(NodePtr node, void (*callback)(NodePtr, void * userdata), void * userdata){
    static NodeVector visited;

    if (!NodeIsInVector(node, visited)) {
        visited.push_back(node);
        if (callback){
            callback(node, userdata);
        }
    }

    for (auto&& n : node->children){
        if (!NodeIsInVector(n, visited)){
            DFS(n, callback, userdata);
        }
    }
}

驱动代码

这里的回调只计算节点和增量和整数。假设 root 是一个 Node 指针并且定义了一个树。树有7个节点,所以count的值在遍历后预计为7。

int count = 0;
DFS(root, [](NodePtr node, void * count){ ++*(int*)count; }, (void*) &count);
std::cout << "DFS count: there are " << count << " nodes.\n";

但是输出是: DFS count: there are 0 nodes. 回调被调用(通过输出到标准输出来验证)。问题是 userdata 变量没有得到更新。

我的问题是:

  • 这是实现我想要的有效方法吗?有更好的方法吗?
  • 我犯了什么错误?

【问题讨论】:

  • void(*)(void*) 指针和void* 数据对适用于 C 回调,但在 C++ 中我通常更喜欢 std::function&lt;void()&gt;。 (或尽可能使用模板参数。)

标签: c++ graph depth-first-search traversal


【解决方案1】:

我认为您的代码的问题在于,如果节点在调用回调之前没有子节点,则您会返回。这将导致只有父节点调用回调。

我会完全删除 is children empty 检查。如果节点没有子节点,则 for 循环不会执行。

void DFS(NodePtr node, void (*callback)(NodePtr, void * userdata), void * userdata){
    static NodeVector visited;

    if (!NodeIsInVector(node, visited)) {
        visited.push_back(node);
        if (callback){
            callback(node, userdata);
        }
    }

    for (auto&& n : node->children){
        if (!NodeIsInVector(n, visited)){
            DFS(n, callback, userdata);
        }
    }
}

【讨论】:

  • 您说的对,孩子的空检查是多余的,我从代码中删除了它。但这并没有解决问题(删除后结果仍然是“DFS计数:有0个节点”)。我还将编辑我的问题以反映这些变化。
  • @EmilePapillon-Corbeil 可能是NodeVectorNodeIsInVector 的问题吗?我尝试用 unordered_set 访问它,它可以工作!
  • 有趣的是它对你有用。我将 NodeVector 和 NodeIsInVector 的代码添加到我的问题中。我找不到可能出了什么问题。遍历本身的工作方式应该是这样:如果我进行回调,只需打印节点的 ID,它就可以工作。
【解决方案2】:

如果我错了,请纠正我,但我怀疑问题是由于在 DFS 中定义的 static 变量造成的。

我在我的问题中没有提到 DFS 是使用不同的回调调用的,该回调在使用回调调用之前不会增加计数变量。我虽然这与我的问题无关。

在函数中声明一个静态变量会导致回调函数在第二次调用期间不被替换吗?

以下版本按预期工作。我还用 std::function 替换了函数指针,反映了 cmets 中提出的一些建议,但更重要的是,我创建了一个声明 visited NodeVector 的包装器,这让我没有任何 static 声明。

void DFS(NodePtr &node, std::function<void(NodePtr)> callback){
    NodeVector visited;
    _DFS(node, callback, visited);
}

void _DFS(NodePtr &node, std::function<void(NodePtr)> callback, NodeVector& visited){
    if (!NodeIsInVector(node, visited)) {
        visited.push_back(node);
        callback(node);
    } else {
        for(auto item : visited){
            std::cout<<item->id << " ";
        }
        std::cout << "\n";
    }
    for (auto&& n : node->children){
        if (!NodeIsInVector(n, visited)){
            _DFS(n, callback, visited);
        }
    }
}

以及驱动代码:

int count = 0;
DFS(root, [&count](NodePtr node) mutable { ++count; });
std::cout << "DFS count: there are " << count << " nodes.\n";

生产:

DFS 计数:有 7 个节点

请注意,使用 static NodeVector visited; 而不是包装器策略的相同功能无法按预期工作。我仍然不是 100% 清楚为什么。

【讨论】:

  • 是的,这就是问题所在。当您第一次使用static NodeVector visited 两次调用DFS 时,第一次调用的所有节点仍将保留在visited 中。因此,您对DFS 的第二次调用将立即返回,因为所有节点都已经在visited 中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-16
  • 2016-03-01
相关资源
最近更新 更多