适用范围
广度优先搜索可在非加权图中查找最短路径,Dijkstra算法是可在加权图中解决单源最短路径问题。
仅当权重为正的时候Dijkstra算法才管用,带有负权边的图不能够使用Dijkstra算法,应该使用Bellman-Ford算法
主要思路
- 找出最近结点
- 更新这个结点邻接点的开销
- 重复步骤1,步骤2,直到对图中所有结点都进行了这两个步骤
- 计算最终路径
实例介绍
假设我们需要找出从结点1到结点6的最短路径
为了实现我们刚才设想的思路,我们需要记录三种信息,可分别用三个散列表来记录:
- 图的信息:每个结点和它的邻接点
- 实时的距离信息:当前从起点到某点的距离为多少(后文会详细解释)
- 结点的父节点:路径上该结点的前一个结点
初始化表一edges(图的信息):
结点 | 邻接点 | 边权重 |
---|---|---|
1 | 2 | 2 |
1 | 3 | 5 |
2 | 3 | 8 |
2 | 4 | 7 |
5 | 4 | 6 |
3 | 5 | 4 |
3 | 4 | 2 |
4 | 6 | 1 |
5 | 6 | 3 |
初始化表二dist(已有的距离信息):
在进行算法之前,起点到每个结点的距离都为无穷大
结点 | 距离 |
---|---|
1 | 0 |
2 | ∞ |
3 | ∞ |
4 | ∞ |
5 | ∞ |
6 | ∞ |
初始化表三par(结点的父节点):
在进行算法之前,可将每个结点的父节点都设为未知结点
结点 | 父节点 |
---|---|
1 | 0 |
2 | 0 |
3 | 0 |
4 | 0 |
5 | 0 |
6 | 0 |
此外我们还需要一个表来记录某结点是否已经处理过,因为对一个结点重复处理是没有意义的。我们可以用一个向量known来记录已经处理过的结点
known | … |
---|
1.找出最近结点
找出最近节点:找出表二中距离最近且未处理过的结点,此时只有起点的距离为0,其他结点的距离为无穷大,因此我们先处理起点,并将起点添加到known中,表示已经处理;
2.更新这个结点邻接点的开销
起点到结点2的距离为2,该距离小于结点2的已有距离无穷大,故更新结点2的已有距离为2,同理将结点3的已有距离更新为5
结点 | 距离 |
---|---|
1 | 0 |
2 | 2 |
3 | 5 |
4 | ∞ |
5 | ∞ |
6 | ∞ |
known | 1 |
---|
重复步骤1,2直到每个结点都已经处理过
后面为了简便叙述将表二表三和表示已经处理结点的表放在一起
结点 | 距离(dist) | 父节点(par) | 是否处理过 |
---|---|---|---|
1 | 0 | 0 | true |
2 | 2 | 1 | false |
3 | 5 | 1 | false |
4 | ∞ | 0 | false |
5 | ∞ | 0 | false |
6 | ∞ | 0 | false |
1.找出最近结点
此时最近的未处理结点是结点2,我们将其标记为已处理。
known | 1 | 2 |
---|
2. 更新这个结点邻接点的开销
结点2到结点3的距离为8,此时需要判断的是否需要更新结点3的距离信息,结点3的已有距离是5,也就是说目前从起点到达结点3的最短距离是5。那么是否从起点经过结点2再到达结点3更近呢,
- dist[3] = 5
- dist[2] + edges[2][3] = 2 + 8 = 10
- 5 < 10
- 从起点直接到结点3更近,因此无需更新
同时结点2到结点4还有一条距离为7的边,也是同样的比较方法
- dist[4] = ∞
- dist[2] + edges[2][4] = 2 + 7 = 9
- 9 < ∞
- 更新dist[4] = 9,par[4] = 2
结点 | 距离(dist) | 父节点(par) | 是否处理过 |
---|---|---|---|
1 | 0 | 0 | true |
2 | 2 | 1 | true |
3 | 5 | 1 | false |
4 | 9 | 2 | false |
5 | ∞ | 0 | false |
6 | ∞ | 0 | false |
我们重复步骤1和步骤2,直到所有结点都已处理。随后的处理如下:
1.找出最近结点
结点 | 距离(dist) | 父节点(par) | 是否处理过 |
---|---|---|---|
1 | 0 | 0 | true |
2 | 2 | 1 | true |
3 | 5 | 1 | true |
4 | 9 | 2 | false |
5 | ∞ | 0 | false |
6 | ∞ | 0 | false |
2. 更新这个结点邻接点的开销
结点 | 距离(dist) | 父节点(par) | 是否处理过 |
---|---|---|---|
1 | 0 | 0 | true |
2 | 2 | 1 | true |
3 | 5 | 1 | true |
4 | 7 | 3 | false |
5 | 9 | 3 | false |
6 | ∞ | 0 | false |
1.找出最近结点
结点 | 距离(dist) | 父节点(par) | 是否处理过 |
---|---|---|---|
1 | 0 | 0 | true |
2 | 2 | 1 | true |
3 | 5 | 1 | true |
4 | 7 | 3 | true |
5 | 9 | 3 | false |
6 | ∞ | 0 | false |
2. 更新这个结点邻接点的开销
结点 | 距离(dist) | 父节点(par) | 是否处理过 |
---|---|---|---|
1 | 0 | 0 | true |
2 | 2 | 1 | true |
3 | 5 | 1 | true |
4 | 7 | 3 | true |
5 | 9 | 3 | false |
6 | 8 | 4 | false |
1.找出最近结点
结点 | 距离(dist) | 父节点(par) | 是否处理过 |
---|---|---|---|
1 | 0 | 0 | true |
2 | 2 | 1 | true |
3 | 5 | 1 | true |
4 | 7 | 3 | true |
5 | 9 | 3 | false |
6 | 8 | 4 | true |
2. 更新这个结点邻接点的开销
结点6无邻接点
1.找出最近结点
结点 | 距离(dist) | 父节点(par) | 是否处理过 |
---|---|---|---|
1 | 0 | 0 | true |
2 | 2 | 1 | true |
3 | 5 | 1 | true |
4 | 7 | 3 | true |
5 | 9 | 3 | true |
6 | 8 | 4 | true |
2. 更新这个结点邻接点的开销
结点5和结点4与结点6邻接
- dist[5] + edges[5][4] = 9 + 6 = 15 > dist[4]
- 结点4无需更新
- dist[5] + edges[5][6] = 9 + 3 = 12 > dist[6]
- 结点6无需更新
结点 | 距离(dist) | 父节点(par) | 是否处理过 |
---|---|---|---|
1 | 0 | 0 | true |
2 | 2 | 1 | true |
3 | 5 | 1 | true |
4 | 7 | 3 | true |
5 | 9 | 3 | true |
6 | 8 | 4 | true |
至此所有的结点都已处理
known | 1 | 2 | 3 | 4 | 6 | 5 |
---|
我们可以输出dist[6] = 8表示起点到结点6的最短距离为8。那么如何输出整条路径呢,由于起点的父节点为0,我们可以通过递归的方法输出
void print_path(char node)
{
if(par[node] == '0')
{
cout<<"the distance is "<<dist[end]<<endl;
cout<<node;
}
else
{
print_path(par[node]);
cout<<"->"<<node;
}
}
代码实现
/**
* @author jump
* @description Dijkstra Algorithm
* @created 2021-02-02
* @last-modified 2021-02-15
*/
#include <bits/stdc++.h>
using namespace std;
const int inf = 666;
class Gragh
{
public:
map<char, map<char, int>> edges;
map<char, int> dist;
map<char, char> par;
vector<char> known;
int size;
char start;
char end;
char find_smallest_cost_node()
{
char ans = '@';
int lowest_cost = inf;
for (auto t : dist)
{
if (find(known.begin(), known.end(), t.first) == known.end()) //!not in
{
if (t.second < lowest_cost)
{
lowest_cost = t.second;
ans = t.first;
}
}
}
return ans;
}
void init()
{
char start, end;
int dist, edge;
cout << "how many nodes?" << endl
<< "how many edges?" << endl
<< "please input the list" << endl;
cin >> size >> edge;
//^in such format: beginning ending weight
while (edge--)
{
cin >> start >> end >> dist;
edges[start][end] = dist;
Gragh::dist[start] = inf;
par[start] = '0';
}
}
void set()
{
cout << "please set the starting node and ending node" << endl;
cin >> start >> end;
dist[start] = 0;
edges[end] = {};
dist[end] = inf;
par[end] = '0';
}
void Dijkstra()
{
while (1)
{
char node = find_smallest_cost_node();
if (node == '@')
break;
known.push_back(node);
for (auto t : edges[node])
{
if (find(known.begin(), known.end(), t.first) == known.end() && dist[node] + t.second < dist[t.first]) //^update
{
dist[t.first] = t.second + dist[node];
par[t.first] = node;
}
}
}
}
//another way to print the path:
/* void output()
{
stack<char> lst;
while (end != '0')
{
lst.push(end);
end = par[end];
}
while (!lst.empty())
{
end = lst.top();
cout << end;
lst.pop();
if (lst.empty())
break;
cout << "->";
}
cout << endl
<< "The distance is:" << dist[end] << endl;
} */
void print_path(char node)
{
if (par[node] == '0')
{
cout<<"the distance is "<<dist[end]<<endl;
cout << node;
}
else
{
print_path(par[node]);
cout << "->" << node;
}
}
};
int main()
{
Gragh g;
g.init();
g.set();
g.Dijkstra();
g.print_path(g.end);
}
/* test input :
6
9
1 2 2
1 3 5
2 3 8
2 4 7
3 4 2
3 5 4
5 4 6
4 6 1
5 6 3
1 6
*/
输入输出样例如下图所示
欢迎指正