本文是文章《一文解决图论中有向图、无向图、非负加权图的单源最短路径问题【超详细】【通用模板程序】【深入分析】【无需基础】【c++】》的附属文章,用于补充解释,主文章中介绍的解决图论中有向图、无向图、非负加权图的单源最短路径问题的通用程序模板中的核心寻路算法的函数findpath的相关内容,上述主文章的链接如下:
https://blog.csdn.net/qq_44339029/article/details/139861959【点击可跳转】
一、、findpath函数源码 【by慕羽】
// 创建节点数据类型,记录节点累计代价值,父节点索引值,节点状态等信息
struct Node
{
int val_;
int parent_;
Node(int val, int parent) : val_(val), parent_(parent) {}
Node() : val_(INT_MAX), parent_(0){}
};
// 寻找最短路径
// 形参依次为 起点索引start_i,目标点索引end_i,搜索图grid,已经扩展过的节点列表closelist
// 返回值表示是否找到可行路径
bool findpath( const int start_i, const int end_i, const vector<vector<pair<int, int>>>& grid, unordered_map<int, Node>& closelist)
{
closelist.clear(); //清空closelist列表,准备存储规划结果
unordered_map<int, Node> openlist; // 创建哈希表存放待扩展点
vector<int> state(grid.size(),1); // 记录节点状态 1:未被访问过 2;在优先级队列中等待扩展 3:已经扩展过了
Node start(0, -1); //初始化起点,代价值为0,起点无父节点设置为-1
openlist[start_i] = start; //将起点放入openlist列表
state[start_i] = 2; //更新起点状态,完成初始化
while (!openlist.empty())
{
//第一步:从openlist列表中选取代价值val最小的节点current_index
int current_index = -1;
int min_val = INT_MAX;
for (const auto& item : openlist) {
if (item.second.val_ < min_val) {
min_val = item.second.val_;
current_index = item.first;
}
}
//第二步:将节点current_index从带扩展队列openlist中取出,并添加到扩展完成队列closelist
Node current = openlist[current_index];
openlist.erase(current_index);
closelist[current_index] = current;
state[current_index] = 3;
//第三步: 判断是否发现到目标点的最短路径
//若当前扩展点是目标点,说明待扩展点队列中其他点的当前代价均大于目标点的代价,图中所有边为非负数,此时已经找到最短路径
if (current_index == end_i) return true;
//第四步: 扩展当前节点current_index
for (const auto &neig: grid[current_index])
{
if (state[neig.first] == 1) //若该邻居节点未被访问过,则将current_index作为其父节点,并放入队列中
{
Node temp(current.val_ + neig.second,current_index);
openlist[neig.first] = temp;
state[neig.first] = 2;
}
else if (state[neig.first] == 2) //若该邻居节点已经在带扩展队列中了,则判断是否需要更新代价值和父节点
{
if (openlist[neig.first].val_ > (current.val_ + neig.second))
{
openlist[neig.first].val_ = current.val_ + neig.second;
openlist[neig.first].parent_ = current_index;
}
} //若该邻居节点已经在带扩展完成队列中了,则无需任何处理
}
}
return false;
}
二、、findpath函数的算法流程概括【by慕羽】
① 将起点放入到openlist中完成算法初始化。
② 检查当前openlist是否为空。如果为空,则搜索失败,停止循环,不存在从起点到目标点的可行路径。若不为空,则继续循环。
③从openlist中取出代价值最小的节点作为当前扩展点current,并将其加入到closelist中。
④ 判断当前扩展点current是否为要找的终点,则说明已经找到了从起点到终点的最短路径,结束循环,若不是,则继续循环。
⑤ 对当前节点current进行扩展,即遍历其当所有的邻接点,生成一组子节点。对于每一个邻接点:
如果该节点在closelist中,则说明该节点已经扩展过了,无需任何处理
如果该节点在openlist中,则说明该节点之前已经被访问过,且添加到openlist中等待被扩展,检查其通过当前节点计算得到的代价值是否比其现在存储的代价值更小,如果更小,则说明我们找到了到节点的一条更优的路径,则更新其代价值,并将其父节点设置为当前节点。
如果该节点不在openlist中,则说明该节点之前没有被访问过,将其加入到openlist列表,并计算代价值,设置其父节点为当前节点。
⑥ 本轮迭代结束, 转到第②步,循环进行下一轮迭代和扩展。直至openlist为空,在第②步中退出循环,或者找到最短路径,在第④步中退出循环。
三、、findpath函数源码解析【by 慕羽 、Chatgpt 4o】
概述
findpath
函数的主要作用是找到从起点到目标点的最短路径。它使用了一种基于优先级队列的搜索算法来实现这一点,类似于Dijkstra算法。函数通过逐步扩展节点并更新其代价值来寻找路径,最终返回一个布尔值,指示是否找到了可行路径。
函数详细介绍
函数开头及初始化
bool findpath( const int start_i, const int end_i, const vector<vector<pair<int, int>>>& grid, unordered_map<int, Node>& closelist)
{
closelist.clear(); //清空closelist列表,准备存储规划结果
unordered_map<int, Node> openlist; // 创建哈希表存放待扩展点
vector<int> state(grid.size(),1); // 记录节点状态 1:未被访问过 2;在优先级队列中等待扩展 3:已经扩展过了
- closelist.clear();
:清空已经扩展过的节点列表closelist
,为新的路径搜索做准备。
- unordered_map<int, Node> openlist;
:创建一个哈希表openlist
,用来存储待扩展的节点。
- vector<int> state(grid.size(), 1);
:创建一个向量state
,记录每个节点的状态。初始状态为1,表示未被访问过。
初始化起点
Node start(0, -1); //初始化起点,代价值为0,起点无父节点设置为-1
openlist[start_i] = start; //将起点放入openlist列表
state[start_i] = 2; //更新起点状态,完成初始化
- Node start(0, -1);
:初始化起点节点,代价值为0,无父节点(父节点索引设为-1)。
- openlist[start_i] = start;
:将起点节点放入openlist
中。
- state[start_i] = 2;
:更新起点状态为2,表示在优先级队列中等待扩展。
主循环 - 从openlist
中选取代价最小的节点
while (!openlist.empty())
{
//第一步:从openlist列表中选取代价值val最小的节点current_index
int current_index = -1;
int min_val = INT_MAX;
for (const auto& item : openlist) {
if (item.second.val_ < min_val) {
min_val = item.second.val_;
current_index = item.first;
}
}
- while (!openlist.empty())
:当openlist
不为空时,执行循环。
- 这段代码的作用是从openlist
中选取代价值最小的节点,保存其索引到current_index
中。
将节点从openlist
移到closelist
,并检查是否到达目标
//第二步:将节点current_index从带扩展队列openlist中取出,并添加到扩展完成队列closelist
Node current = openlist[current_index];
openlist.erase(current_index);
closelist[current_index] = current;
state[current_index] = 3;
//第三步: 判断是否发现到目标点的最短路径
//若当前扩展点是目标点,说明待扩展点队列中其他点的当前代价均大于目标点的代价,图中所有边为非负数,此时已经找到最短路径
if (current_index == end_i) return true;
- Node current = openlist[current_index];
:获取当前节点。
- openlist.erase(current_index);
:将当前节点从openlist
中移除。
- closelist[current_index] = current;
:将当前节点加入closelist
。
- state[current_index] = 3;
:更新当前节点状态为3,表示已经扩展过。
- if (current_index == end_i) return true;
:如果当前节点是目标节点,返回true
,表示找到路径。
扩展当前节点的邻居节点
//第四步: 扩展当前节点current_index
for (const auto &neig: grid[current_index])
{
if (state[neig.first] == 1) //若该邻居节点未被访问过,则将current_index作为其父节点,并放入队列中
{
Node temp(current.val_ + neig.second,current_index);
openlist[neig.first] = temp;
state[neig.first] = 2;
}
else if (state[neig.first] == 2) //若该邻居节点已经在带扩展队列中了,则判断是否需要更新代价值和父节点
{
if (openlist[neig.first].val_ > (current.val_ + neig.second))
{
openlist[neig.first].val_ = current.val_ + neig.second;
openlist[neig.first].parent_ = current_index;
}
} //若该邻居节点已经在带扩展完成队列中了,则无需任何处理
}
}
return false;
}
- for (const auto &neig: grid[current_index])
:遍历当前节点的所有邻居节点。
- if (state[neig.first] == 1)
:如果邻居节点未被访问过,将其代价值更新,并放入openlist
。
- Node temp(current.val_ + neig.second, current_index);
:创建新的节点,其代价值为当前节点代价值加上边的权重。
- openlist[neig.first] = temp;
:将新的节点加入openlist
。
- state[neig.first] = 2;
:更新邻居节点状态为2,表示在优先级队列中等待扩展。
- else if (state[neig.first] == 2)
:如果邻居节点已经在openlist
中,判断是否需要更新其代价值和父节点。
- if (openlist[neig.first].val_ > (current.val_ + neig.second))
:如果新代价值更小,则更新邻居节点的代价值和父节点。
总结
findpath
函数通过维护两个容器(openlist
和closelist
)以及一个状态向量state
,逐步扩展节点并寻找从起点到目标点的最短路径。如果找到了路径,返回true
,否则返回false
。