【问题标题】:Efficient way of finding minimum path in graph traversal在图遍历中找到最小路径的有效方法
【发布时间】:2021-02-15 14:58:27
【问题描述】:

我有一大组二进制字符串,长度都一样。我知道每个二进制字符串的 1 步邻域(意思是,所有其他二进制字符串仅相差 1 位)。这种关系本质上创建了一个图表。现在,从图中的任何节点开始,我想知道在基于某些派生值(不是基于二进制字符串)到达不同节点之前我必须采取的最小步骤数。我正在使用一些下面的示例中的虚拟函数 GetABC() )。如果我们在找到不同节点之前遇到死胡同,我们假设到达不同节点的最小步数是无限的。死胡同是指无法访问新节点(基于访问节点的历史)。如果我们的整个种群具有相同的派生值,那么最坏的情况是我们会访问每个节点然后返回无穷大 (-1)。

我的第一次尝试是使用递归函数,并通过传递对访问节点向量的引用来跟踪所有访问节点。它适用于小型数据集,但使用 100 万个二进制字符串时,我遇到了堆栈溢出错误(字面意思)。

然后,我重构了我的代码以循环运行,而不是通过迭代构建一组要访问的邻居然后遍历所有邻居并检查是否有任何具有不同的派生值 (GetABC()),然后从所有我们刚去过的邻居。同样,在一个小型数据集上工作,但有 100 万条记录,我的代码已经运行了 4 天,但仍未完成(在 12 个线程上使用并行 for_each 对所有节点进行此分析)。

//Previously had this function as a recursive algorithm, but it triggered stack overflow on big dataset
int MyClass::FindDifferentNodeInNeighborhood2(uint64_t node_hash) {
    std::vector<uint64_t> visited;//init empty visited vector
    visited.push_back(node_hash);

    int level = 0;
    bool found_diff_node = false;
    std::unordered_map<int, std::vector<std::pair<uint64_t, uint64_t>>> to_visit;//<level, vec<node_to_visit, parent_node>>

    to_visit[level].push_back(std::make_pair(node_hash, 0));//0 because no parent. Not used in first loop anyway so the value doesn't matter

    while (!found_diff_node && !to_visit[level].empty()) {
        for (auto n : to_visit[level]) {
            //_negihborhood is a member variable of MyClass defined as
            //std::unordered_map<uint64_t, std::unordered_set<uint64_t>> _neighborhood;
            for (const auto neighbor : _neighborhood[n.first]) {
                //make sure we didn't visit this neighbor already
                if (std::find(visited.begin(), visited.end(), neighbor) == visited.end()) {
                    //if not, add to next set of node to visit
                    to_visit[level + 1].push_back(std::make_pair(neighbor, n.first));
                }
            }
        }
        level++;

        for (auto n : to_visit[level]) {
            //auto node = n.first;
            //auto parent = n.second;
            visited.push_back(n.first);
            //_population is the map of the actual binary strings objects
            //We retrieve the object using it's hash
            //GetABC() is just a function that gets a specific value from the node
            if (_population[n.first].GetABC() != _population[n.second].GetABC()) {
                found_diff_node = true;
                break;
            }
        }
    }

    if (!found_diff_node) {
        return -1;
    }
    return level;
}

我会进一步调试,看看它是否不是无限循环的问题,但我想我应该在这里问我是否没有忽略一些可以实现这一点的简单算法。

谢谢

【问题讨论】:

  • 您正在使用哪些编译器选项(具体来说,您是否启用了优化)?
  • 这里有很多东西要解压。 1) 数百万 记录? 有多少 节点? 如果您正在搜索每个字符串的一步邻域,那么您的搜索空间很大,并且有可用的捷径. 2)是的,在这种情况下递归是一种不好的方法。 3)你的实际算法是什么?你熟悉 Dijkstra 吗?
  • @1201ProgramAlarm 我正在使用 MSVC 2019 编译,使用已启用优化的发布配置(不记得是 O2 还是 O3)
  • @Beta 好问题。每条记录 1 个节点,因此 100 万个节点。实际上,构建一步邻域的地图很好,问题实际上是检查 n 步邻域,直到派生值不同。我不知道我实现的算法的名称是什么,但它与 Dijkstra 有点不同,虽然结果是一样的

标签: c++ optimization graph-traversal


【解决方案1】:

visited 应更改为unordered_set,并对其使用方式进行相关更改。这将大大减少搜索节点是否已被访问所花费的时间。这可能会增加所需的内存。

to_visit 看起来不需要是unordered_map。在将新节点添加到下一个级别时,您可以查看带有level 的节点。然后,您查看那些新节点,而不再查看以前的节点。将此映射替换为两个 std::vector&lt;std::pair&lt;uint64_t, uint64_t&gt;&gt; 变量(我将它们称为 visit_now 和 visit_next。对 to_visit[level] 的引用将指向 visit_now,而添加到 to_visit[level + 1] 将指向 visit_next。当你离开第一个循环,当你增加level时,你可以交换visit_next和visit_now,然后清除visit_next(或者移到现在并清除下一个)。

当添加到visit_next(又名to_visit[level + 1])向量(visit_next.emplace_back(neighbor, n.first));)时,使用emplace_back而不是push_back。

【讨论】:

  • 虽然内存利用率现在不是问题,但拥有更精简的代码总是很好的。感谢您提出这些建议。
【解决方案2】:

发现我的第一个循环多次添加相同的节点,因为从要访问的节点集中很可能其中一些节点有几个共同的邻居。通过擦除要访问的节点集中的重复项轻松修复。我还实施了另一个答案中提出的建议。结果如下:

int MyClass::FindDifferentNodeInNeighborhood2(uint64_t node_hash) {
    std::unordered_set<uint64_t> visited;//init empty visited vector
    visited.insert(node_hash);
    

    int level = 0;
    bool found_diff_node = false;
    std::vector<std::pair<uint64_t, uint64_t>> visit_now;
    std::vector<std::pair<uint64_t, uint64_t>> visit_next;

    visit_now.emplace_back(std::make_pair(node_hash, 0));

    while (!found_diff_node && !visit_now.empty()) {
        for (auto n : visit_now) {
            for (const auto neighbor : _neighborhood[n.first]) {
                if(visited.find(neighbor) == visited.end()){
                    visit_next.emplace_back(std::make_pair(neighbor, n.first));
                }
            }
        }
        level++;

        //Remove duplicates
        std::sort(visit_next.begin(), visit_next.end());
        visit_next.erase(std::unique(visit_next.begin(), visit_next.end()), visit_next.end());


        visit_now.clear();
        visit_now = std::move(visit_next);
        visit_next.clear();

        for (auto n : visit_now) {
            visited.insert(n.first);
            if (_population[n.first].GetABC() != _population[n.second].GetABC()) {
                found_diff_node = true;
                break;
            }
        }
    }

    if (!found_diff_node) {
        return -1;
    }
    return level;
}

【讨论】:

    猜你喜欢
    • 2010-09-18
    • 2015-07-21
    • 1970-01-01
    • 2015-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-04
    • 1970-01-01
    相关资源
    最近更新 更多