问题描述:
从一个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;
}
存在的问题:运行很慢。
优化方案:
- 使用队列代替列表:在原始实现中,使用了 std::list 来存储待访问的节点。虽然 std::list 提供了高效的插入和删除操作,但对于广度优先搜索(BFS)而言,使用 std::queue 可能更为合适,因为它不需要频繁地移动元素。
- 减少内存分配:在原始实现中,每当遇到新的邻接顶点时都会创建一个新的 LineNode 实例。这会导致大量的动态内存分配和析构操作。我们可以尝试重用现有的 LineNode 实例来减少内存分配次数。
- 避免不必要的复制:在原始实现中,每次创建新节点时都会复制路径 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;
}
效果:
快到飞起!