📖 Dijkstra算法描述
🤔问题描述:
给定带权有向图G = (V,E)
,V代表图的顶点集,E代表图的边集,其中每条边的权值为非负值。另外还给定了V中的一个顶点,称为源点(所求起始顶点),计算从源点到其他各顶点的最短路径长度(为最短路径上各边权值之和)。
如下有向带权图:
🌴 Dijkstra的理解
[1] 初始化:先找出从源点到各终点vk的直达路径( v 0 v_0 v0, v k v_k vk),即仅通过一条弧到达的路径。并且将权值设为当前路径的权值(权值长度保存在一个一维数组中 D[] ),否则为
∞
;选择:从这些直达路径中找出一条长度最短的路径(v0,u)
;
[2] 选择:从这些直达路径中找出一条长度最短的路径( v 0 v_0 v0,u);
[3] 更新:对其余各条路径进行适当的调整,若在图中存在弧,若在图中存在弧(u, v k v_k vk),且满足
(v0,u)+ (u,vk) < (v0,vk)
,则将最小的路径(v0,u,vk)替换(v0,vk)
。
[4] 重复推导,直到所有的其他顶点被推导完为止。
🍃 Dijkstra的思想(贪心选择)
🔥
按路径长度递增次序产生最短的路径;
1️⃣ 设置两个一维数组P[MAX_VERTEX_NUM],D[MAX_VERTEX_NUM], P[]存放从起始顶点当前到指定顶点所已经访问的顶点的最短路径的前驱节点,D[i] 的每个分量表示当前所找到的从源点到每个终点的最短路径的长度,它的初始态为:若从源点到 v j v_j vj 有直达的弧边,则为该弧边的权值,否则 D[i] 为无 ∞
:
此时的D[i]路径长度值为源点到 v j v_j vj 的最短路径,为(v,vj)
;
2️⃣ 增设一个一维数组flag[MAX_VERTEX_NUM], 此数组实际存放长度为节点数,当flag[i] = 1代表了源点到顶点 v j v_j vj的最短路径已经被访问。
3️⃣ 算法步骤:
- 设带权领接矩阵arcs表示此有向带权图,
arcs[i][j]表示弧<vi,vj>上的权值
,若不存在则设为∞
,用S代表已找到从v出发的最短路径的终点的集合,它的初始状态为空。接下来从v出发到图中上其余顶点(终点) v i v_i vi可能到达的最短路径长度的初值为:
- 选择 v i v_i vi使得:
v i v_i vi为当前求得的一条从v出发的最短路径终点,令:S = S ∪{j}
- 修改从v出发的集合V-S 上任一顶点 v k v_k vk可达的最短路径长度,如果:
D[j] + arcs[j][k] < D[k]
则修改D[k]的值:D[k] = D[j] + arcs[j][k]
4.重复(2),(3)步骤n-1次(少了源点的步骤)
📝 总体设计
//存储结构选择
typedef struct ArcCell
{
VRType adj; //边权值
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct
{
VRType vexs[MAX_VERTEX_NUM]; //顶点向量
AdjMatrix arcs; //邻接矩阵
int vexnum, arcnum; //图的当前顶点数和弧数
} MGraph;
//函数
bool CreateDNG(MGraph& G);
int LocateVex(MGraph G, VertexType v);
void PrintDNG(MGraph G);
void ShortPath_DIJ(MGraph G, int v0, int P[], int D[]);
📝 详细设计
//迪杰斯特拉算法
/* v0: 单源节点起始顶点
P[]: 单源节点v0到其余节点的最短路径的一堆数组
D[]: 顶点v到其他顶点的最短距离的数组
*/
void ShortPath_DIJ(MGraph G, int v0, int P[], int D[])
{
int i, j, k;
int min;
int tmp;
int flag[MAX_VERTEX_NUM];
// flag[i]=1表示源点v0到顶点vi的最短路径已经访问
// 初始化
for (i = 0; i < G.vexnum; i++)
{
flag[i] = 0; // 顶点i的最短路径未被访问
P[i] = 0;
D[i] = G.arcs[v0][i].adj; // 顶点i的最短路径为源点v0到顶点vi的权值
}
// 对源点自身进行初始化
flag[v0] = 1;
D[v0] = 0;
// 遍历n-1次;每次找出一个顶点的最短路径;
for (i = 1; i < G.vexnum; i++)
{
// 寻找当前最小的路径;
// 在未获取最短路径的顶点中,找到离v0最近的顶点
min = INFINITY;
//开始寻找v0与当前所有能直达的最短路径顶点,遍历数组D[]找出最小路径
for (j = 0; j < G.vexnum; j++)
{
if (flag[j] == 0 && D[j] < min)
{
min = D[j];
k = j;
}
}
// 标记k顶点为已经获取到最短路径
flag[k] = 1;
// 修正当前最短路径,使用贪心策略对当前已经加入进来的顶点在D数组的最短路径进行更新
for (j = 0; j < G.vexnum; j++)
{
tmp = (G.arcs[k][j].adj == INFINITY ? INFINITY : (min + G.arcs[k][j].adj));
if (flag[j] == 0 && (tmp < D[j]))
{
D[j] = tmp;
P[j] = k;
}
}
}
}
🍋 源码
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
#define INFINITY INT_MAX //最大值无边为∞,对角线为0,反之为权值
#define MAX_VERTEX_NUM 20 //最大顶点数
typedef int VRType;
typedef int VertexType;
typedef struct ArcCell
{
VRType adj; //边权值
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct
{
VRType vexs[MAX_VERTEX_NUM]; //顶点向量
AdjMatrix arcs; //邻接矩阵
int vexnum, arcnum; //图的当前顶点数和弧数
} MGraph;
bool CreateDNG(MGraph& G);
int LocateVex(MGraph G, VertexType v);
void PrintDNG(MGraph G);
void ShortPath_DIJ(MGraph G, int v0, int P[], int D[]);
//定位
int LocateVex(MGraph G, VertexType v)
{
int i;
for (i = 0; i < G.vexnum; i++)
if (G.vexs[i] == v) return i;
return -1;
}
//构建有向网
bool CreateDNG(MGraph& G)
{
cout << "请输入当前顶点数和弧数(空格隔开):";
cin >> G.vexnum >> G.arcnum;
int i, j, k;
int v1, v2, w; ///弧尾和弧头及权值
for (i = 0; i < G.vexnum; i++)
{
cout << "请输入第" << (i + 1) << "个顶点的信息(name):";
cin >> G.vexs[i];
}
cout << endl;
//初始化领接矩阵
for (i = 0; i < G.vexnum; i++)
for (j = 0; j < G.vexnum; j++)
{
G.arcs[i][j].adj = { INFINITY };
}
//对于有向边的确定以及权值的确定
for (k = 0; k < G.arcnum; k++)
{
cout << "请输入第" << (k + 1) << "条边的起点和终点以及权值(空格隔开):";
cin >> v1 >> v2 >> w;
i = LocateVex(G, v1);
j = LocateVex(G, v2);
if (i == -1 || j == -1) return false;
else
G.arcs[i][j].adj = w;
}
return true;
}
//打印有向网
void PrintDNG(MGraph G)
{
int i, j;
cout << "有向网的顶点为:" << endl;
for (i = 0; i < G.vexnum; i++)
cout << "V" << G.vexs[i] << " ";
cout << endl;
cout << "有向网的领接矩阵为:" << endl;
cout << "\t";
for (i = 0; i < G.vexnum; i++)
{
cout << setw(7) << "V" << G.vexs[i];
}
cout << endl;
for (i = 0; i < G.vexnum; i++)
{
cout << "\n" << setw(7) << "V" << G.vexs[i];
for ( j = 0; j < G.vexnum; j++)
{
if (G.arcs[i][j].adj == INT_MAX && i != j) cout << setw(8) << "∞";
else if(i == j) cout << setw(8) << "0";
else
cout << setw(8) << G.arcs[i][j].adj;
}
cout << endl;
}
}
//迪杰斯特拉算法
/* v0: 单源节点起始顶点
P[]: 单源节点v0到其余节点的最短路径的一堆数组
D[]: 顶点v0到其他顶点的最短距离的数组
*/
void ShortPath_DIJ(MGraph G, int v0, int P[], int D[])
{
int i, j, k;
int min;
int tmp;
int flag[MAX_VERTEX_NUM];
// flag[i]=1表示源点v0到顶点vi的最短路径已经访问
// 初始化
for (i = 0; i < G.vexnum; i++)
{
flag[i] = 0; // 顶点vi的最短路径未被访问
P[i] = 0;
D[i] = G.arcs[v0][i].adj; // 顶点i的最短路径为源点v0到顶点vi的权值
}
// 对源点v0自身进行初始化
flag[v0] = 1;
D[v0] = 0;
// 遍历n-1次;每次找出一个顶点的最短路径;
for (i = 1; i < G.vexnum; i++)
{
// 寻找当前最小的路径;
// 在未获取最短路径的顶点中,找到离v0最近的顶点k
min = INFINITY;
//开始寻找v0与当前所有能直达的最短路径顶点,遍历数组D[]找出最小路径
for (j = 0; j < G.vexnum; j++)
{
if (flag[j] == 0 && D[j] < min)
{
min = D[j];
k = j;
}
}
// 标记顶点vk为已经获取到最短路径
flag[k] = 1;
// 修正当前最短路径,使用贪心策略对当前已经加入进来的顶点在D数组的最短路径进行更新
for (j = 0; j < G.vexnum; j++)
{
tmp = (G.arcs[k][j].adj == INFINITY ? INFINITY : (min + G.arcs[k][j].adj)); // 防止溢出
if (flag[j] == 0 && (tmp < D[j]))
{
D[j] = tmp;
P[j] = k;
}
}
}
}
int main()
{
MGraph G;
CreateDNG(G);
PrintDNG(G);
int P[MAX_VERTEX_NUM] = {0};
int D[MAX_VERTEX_NUM] = {0};
int num;
cout << "请输入所求的单源起始顶点(1~6):";
cin >> num;
ShortPath_DIJ(G, num-1, P, D);
cout << endl;
cout << "源点V" << num << "到各顶点的最短路径长度为:" << endl;
for (int i = 0; i < G.vexnum; i++)
{
if (D[i] == INFINITY)
cout << "V" << G.vexs[num - 1] << " -> V" << G.vexs[i] << " = " << "∞" << endl;
else cout << "V" << G.vexs[num - 1] << " -> V" << G.vexs[i] << " = " << D[i] << endl;
}
system("pause");
return 0;
}