DAG路径搜索优化性能提高百倍

问题描述:

从一个DAG图中给定的起点 begin_line 寻找一条路径到达给定的终点 end_line。
图的拓扑关系由 top 这个映射(map<int64, vector<int64>>)表示,每条边都有一个或多个邻接的后继。begin_line 和 end_line 都是边的id。
如果找到了这样的路径,将路径保存 vector<int64> path 结构中。如果路径有多条,只需找出任意一条即可。

参考的函数定义:

/*
 * begin_line: 起始边的ID。
 * end_Line: 目标边的ID。
 * top: 一个映射,键是节点的ID,值是一个包含邻接ID的列表。
 * path_r: 输出参数,用于返回找到的路径。
*/
bool GetPath(int64 begin_line, int64 end_line, const map<int64, vector<int64>>& top, vector<int64>& path_r);

初步的实现

使用广度优先遍历的方式寻找路径。

bool GetPath(int64 begin_line, int64 end_Line, const map<int64, vector<int64>>& top, vector<int64>& path_r) {
  struct LineNode {
    int64 line_id;
    vector<int64> path;
    set<int64> exists;
  };

  vector<int64> line_list;
  list<LineNode> visited;
  visited.push_back(LineNode());
  visited.back().line_id = begin_line;
  visited.back().path.push_back(begin_line);
  visited.back().exists.insert(begin_line);

  auto iter = visited.begin();
  while (iter != visited.end()) {
    LineNode& node = *iter;
    if (node.line_id == end_Line) {
      path_r = node.path;
      return true;
    } else {
      auto iter_top = top.find(node.line_id);
      if (iter_top != top.end()) {
        for (int64 next_id : iter_top->second) {
          if (node.exists.find(next_id) != node.exists.end()) {
            continue;
          }
          visited.push_back(LineNode());
          visited.back().line_id = next_id;
          visited.back().path = node.path;
          visited.back().exists = node.exists;
          visited.back().path.push_back(next_id);
          visited.back().exists.insert(next_id);
          //std::cout << next_id << endl;
        }
      }
    }
    ++iter;
  }
  return false;
}

存在的问题:运行很慢。

优化方案:

  1. 使用队列代替列表:在原始实现中,使用了 std::list 来存储待访问的节点。虽然 std::list 提供了高效的插入和删除操作,但对于广度优先搜索(BFS)而言,使用 std::queue 可能更为合适,因为它不需要频繁地移动元素。
  2. 减少内存分配:在原始实现中,每当遇到新的邻接顶点时都会创建一个新的 LineNode 实例。这会导致大量的动态内存分配和析构操作。我们可以尝试重用现有的 LineNode 实例来减少内存分配次数。
  3. 避免不必要的复制:在原始实现中,每次创建新节点时都会复制路径 path 和已访问顶点集合 exists。这可能导致不必要的内存复制开销。我们可以通过引用或指针来避免这种复制。

基于上述考虑,下面是一个经过优化的版本:

#include <iostream>
#include <queue>
#include <set>
#include <vector>
#include <map>
#include <memory>

struct LineNode {
    int64 line_id;
    std::vector<int64> path;
};

bool GetPath(int64 begin_line, int64 end_line, const std::map<int64, std::vector<int64>>& topo, 
    std::vector<int64>& path_r) {
    using NodePtr = std::unique_ptr<LineNode>;

    std::queue<NodePtr> que;  // 使用队列存储指向 LineNode 的智能指针
    std::set<int64> visited_lines;  // 存储已访问的顶点

    NodePtr start_node(new LineNode());
    start_node->line_id = begin_line;
    start_node->path.push_back(begin_line);

    que.push(std::move(start_node));  // 将起始节点放入队列
    visited_lines.insert(begin_line);

    while (!que.empty()) {
        NodePtr current = std::move(que.front());
        que.pop();

        if (current->line_id == end_line) {
            path_r = current->path;
            return true;
        }

        auto iter_topo = topo.find(current->line_id);
        if (iter_topo != topo.end()) {
            for (int64 next_id : iter_topo->second) {
                if (visited_lines.find(next_id) != visited_lines.end()) {
                    continue;
                }

                NodePtr next_node(new LineNode(*current));  // 深拷贝当前节点
                next_node->line_id = next_id;
                next_node->path.push_back(next_id);

                que.push(std::move(next_node));
                visited_lines.insert(next_id);
            }
        }
    }

    return false;
}

int main() {
    std::map<int64, std::vector<int64>> topo = {
        {1, {2, 3}},
        {2, {4}},
        {3, {5}},
        {4, {6}},
        {5, {6}},
        {6, {}}
    };

    std::vector<int64> path;
    bool found = getPath(1, 6, topo, path);

    if (found) {
        for (int64 id : path) {
            std::cout << id << " ";
        }
        std::cout << std::endl;
    } else {
        std::cout << "No path found." << std::endl;
    }

    return 0;
}

效果:

快到飞起!

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值