问题描述:
给定一个图G(V,E),求一条从起点到终点的路径,使得这条路径上经过的所有边的边权之和最小。
(这个问题PAT很容易考哦~)
解决最短路径问题的算法主要有:
Dijkstra算法,BF算法,SPFA算法,Floyd算法
不要求全部掌握啦,但是至少要熟悉一种哦~
接下来来讲第一种:Dijkstra算法~
Dijkstra算法:
这种算法一般用于解决***单源最短路问题***(给定G和起点s,通过算法得到S到达其他每个顶点最短距离)
算法基本思想:
对G设置集合S,存放已经被访问的顶点,然后每次从集合V-S中选择与起点s的最短距离最小的一个顶点(假设为u),访问并且加入集合S之后,令顶点u为中介点,优化起点s与所有从u能到达的顶点v之间的最短距离。这样操作n次,n为顶点个数,直到集合S已经包含所有顶点。
简单来说Dijkstra算法的策略就是:
设置集合S存放已经访问过的顶点,然后执行n次下面两个步骤:
1、每次从集合V-S(未访问过的顶点)中选择与起点s的最短距离中最小的一个顶点(记为u),访问并且加入集合S(即已访问)。
2、之后,令顶点u为中介点,优化起点s与所有从u能到达的顶点v之间的最短距离~
Dijkstra算法的具体实现:
1、集合S可以用一个bool类型的数组vis[]来实现,即当vis[i]=true的时候,表示顶点Vi已经被访问,当vis[i]==false的时候表示顶点Vi未被访问。
2、令int数组d[]表示起点s到达顶点Vi的最短距离,初始的时候除了起点s的d[s]赋为0,其余顶点都赋予一个很大的数,来表示inf,即不可达~
// 伪代码如下:
// G是图,一般设置为全局变量;
// d为源点到各个顶点的最短路径长度,s为起点
Dijkstra(G, d[], s){
初始化;
for(循环n次){
u = 使d[u]最小的还没有被访问的顶点的符号;
记录u已经被访问;
for(从u出发能到达的所有顶点v){
if(v未被访问过&&以u为中介点使得s到达顶点v的最短距离d[v]更优){
优化d[v];
}
}
}
}
// 定义全局变量
constint MAXV = 1000;
constint INF = 1000000000;
1、邻接矩阵模版:
int n, G[MAXV][MAXV];
int d[MAXV];
bool vis[MAXV] = {false};
void Dijkstra(int s){
fill(d, d+MAXV, INF); // 将数组d全部赋值为INF
d[s] = 0;
for(int i = 0; i < n; i++){
int u = -1, MIN = INF;//u使d[u]最小,MIN存放最小的d[u]
for(int j = 0; j < n; j++){//找到未访问的顶点中d[u]最小的
if(vis[j] == false && d[j] < MIN){
u = j;
MIN = d[j];
}
}
//找不到小于INF的d[u],则说明剩下的顶点与s并不连通
if(u == -1) return;
vis[u] = true;
for(int v = 0; v < n; v++){
if(vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]){
d[v] = d[u] + G[u][v];
}
}
}
}
2、邻接表模版
struct Node{
int v, dis;
}
vector<Node> Adj[MAXV];
int n;
int d[MAXV];
bool vis[MAX] = {false};
void Dijkstra(int s){
fill(d, d+MAXV, 0);
d[s] = 0;
for(int i = 0; i < n; i++){
int u = -1, MIN = INF;
for(int j = 0; j < n; j++){
if(vis[j] == false && d[j] < MIN){
u = j;
MIN = d[j];
}
}
if(u == -1) return;
vis[u] = true;
//只有下面这个遍历从u出发的所有顶点v的这个部分与邻接矩阵不同
for(int j = 0; j < Adj[u].size(); j++){
int v = Adj[u][j].v;
if(vis[v] == false && d[u] + Adj[u][j].dis < d[v]){
d[v] = d[u] + Adj[u][j].dis;
}
}
}
}
当我们已经知道了最短距离如何实现,那么接下来来说说最短路径的实现方法~
只需要加一个pre[MAXV]即可~
看看如下算法模版~
int n, G[MAXV][MAXV];
int d[MAXV];
int pre[MAXV];// pre[v]就是从起点到顶点v的最短路径v上的前一个顶点
bool vis[MAXV] = {false};
void Dijkstra(int s){
fill(d, d+MAXV, INF); // 将数组d全部赋值为INF
//新添加,将pre[i]全部初始化其本身
for(int i = 0; i < n; i++) pre[i] = i;
d[s] = 0;
for(int i = 0; i < n; i++){
int u = -1, MIN = INF;//u使d[u]最小,MIN存放最小的d[u]
for(int j = 0; j < n; j++){//找到未访问的顶点中d[u]最小的
if(vis[j] == false && d[j] < MIN){
u = j;
MIN = d[j];
}
}
//找不到小于INF的d[u],则说明剩下的顶点与s并不连通
if(u == -1) return;
vis[u] = true;
for(int v = 0; v < n; v++){
if(vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]){
d[v] = d[u] + G[u][v];
pre[v] = u;
}
}
}
}
这个仔细看起来有点像递归~
我们使用更简洁的递归方式:
void DFS(int s, int v){
if(v == s){
printf("%d\n", s);
return;
}
DFS(s, pre[v]);
printf("%d\n", v);//从最深层return回来之后,输出每一层递归的顶点号
}