最短路径问题(图表详解迪杰斯特拉算法)

首先,我们来看一下相关的图的一些基本知识点

:图 G=(V,E) 由顶点集 V 和边集 E 组成。每条边对应一个点对 (v,w),其中 v,w 属于 V 。如果图中的点对是有序的,那么该图就是有向图,反之为无向图。

邻接点:若顶点 v 与 w 之间存在一条边,则认为顶点 v 与 w 邻接。

:图中的每条边都可以对应一个数值,这种与边相关的数值称为权。

路径:在图 G 中,顶点 v1 到 vk 的路径是一个顶点序列 v1,v2,···,vk。

接下来我们进入正题:

单源最短路径问题,即在带权图中指定一个起始点 v ,找出从 v 到图中其余每个顶点的最短距离。迪杰斯特拉算法是解决单源最短路径问题的一个经典算法。该算法按阶段进行,在每个阶段,算法首先选择一个顶点 u ,该顶点在所有剩余顶点中与起始点 v 的距离最近,然后以此距离为基础,更新起始点 v 与其余未被选中的顶点之间的路径距离,接着继续进行选择,直到所有顶点都被选择到,算法结束。

具体步骤如下:

(1)假设 S 为已求得最短路径的终点的点集,在初始时,S 只包含起始点 v ,而点集 U 包含图中除了顶点 v 以外的所有顶点

(2)从 U 中选择一个顶点 u ,它是源点 v 到 U 的距离最近的一个顶点,将 u 加入 S。

(3)继续从 U 中选择下一个与源点距离最近的顶点。

(4)重复步骤 3 ,直到点集 U 为空

最短路径问题(图表详解迪杰斯特拉算法)_第1张图片

以上方无向有权图为例。具体实现过程中,我们需要创建三个辅助数组 Adjvex[],Lowcost[],Flag[]。Adjvex[i]=j 表示从起始点到达顶点 i 会经过顶点 j (主要用于求出最短路径); Lowcost[i] 表示从起始点到顶点 i 的最短距离(初始化为无限大); Flag[i] 标注顶点 i 是否已被选中(初始化为0)。用图表示程序实现过程如下

1、这里我们以 v3 作为起始点。由于只有一个点,故Adjvex的值全为3; Lowcost 行加入各点到v3 的距离; Flag[3]置为1。如下图所示:

编号 1 2 3 4 5
Adjvex 3 3 3 3 3
Lowcost 8

 \infty

0

 \infty

3
Flag 0 0 1 0 0

2、从剩下未选择的点中找出与源点距离最近的顶点,这里是 v5,将 Flag[5] 置为1。由于加入了新点,我们要更新起始点 v3 与其余未被选中的顶点之间的路径距离,具体做法是:遍历 v5 的所有邻接点,若 v1到 v5 的距离加上 v5 到某邻接点 vk 的距离和小于原来 v1 到 vk 的距离( 即  Lowcost[k] > Lowcost[5] + (v5,vk)的权值 ),则更新 v1 到 vk 的距离,同时令 Adjvex[k]=5。如下图所示:

编号 1 2 3 4 5
Adjvex 3 5 3 5 3
Lowcost 8

 10

0

 4

3
Flag 0 0 1 0 1

3、与第2步相同,从剩下未选择的点中找出与源点距离最近的顶点,这里是 v4,将 Flag[4] 置为1。然后,更新起始点 v3 与其余未被选中的顶点之间的路径距离,具体做法同上:遍历 v4 的所有邻接点,若 v1到 v4 的距离加上 v4 到某邻接点 vk 的距离和小于原来 v1 到 vk 的距离( 即  Lowcost[k] > Lowcost[4] + (v4,vk)的权值 ),则更新 v1 到 vk 的距离,同时令 Adjvex[k]=4。如下图所示:

编号 1 2 3 4 5
Adjvex 4 4 3 5 3
Lowcost 6

 8

0

 4

3
Flag 0 0 1 1 1

4、继续重复上述步骤,直到所有顶点都被选择到,算法结束。最终表格如下:

编号 1 2 3 4 5
Adjvex 4 4 3 5 3
Lowcost 6

 8

0

 4

3
Flag 1 1 1 1 1

程序代码:

# include 
# include 
# define SIZE 20
# define NEWSIZE 20
# define Infinity 100000000   //表示无限大
using namespace std;
typedef struct ArcNode {  //边的结点结构类型
	int adjvex;           //该边的终点编号
	int weight;           //该边的权值
	struct ArcNode* nextarc;  //指向下一条边的指针
}ArcNode;
typedef struct VexNode {  //顶点结构
	int num;              //顶点编号
	ArcNode* firstarc;    //指向第一条与该顶点有关的边的指针
}VexNode;
typedef struct Graph {    //邻接表结构类型
	VexNode* VNode;       //定义邻接表
	int vexnum, arcnum;   //顶点数和边的个数
	int size;             //邻接表的大小
}Graph;

int* Adjvex;  //从起始点到达某顶点会经过的顶点
int* Lowcost; //从起始点到某顶点的最短距离
int* Flag;    //标注顶点是否已被选中(初始化为0)

void Create_Graph(Graph& G)   //创建图的邻接表
{
	cout << "顶点的个数:";
	cin >> G.vexnum;
	G.VNode = (VexNode*)malloc(SIZE * sizeof(VexNode));
	G.size = SIZE;
	while (G.size <= G.vexnum) {   //根据点的个数动态分配空间
		G.VNode = (VexNode*)realloc(G.VNode, (G.size + NEWSIZE) * sizeof(VexNode));
		G.size += NEWSIZE;
	}
	Adjvex = (int*)malloc((G.size + 10) * sizeof(int));
	Lowcost = (int*)malloc((G.size + 10) * sizeof(int));
	Flag = (int*)malloc((G.size + 10) * sizeof(int));
	for (int i = 1; i <= G.vexnum; i++) {
		G.VNode[i].num = i;          //给各点编号
		G.VNode[i].firstarc = NULL;  //邻接表初始化,所有单向链表均为空表
	}
	cout << "请输入边的个数:";
	cin >> G.arcnum;
	cout << "请输入各边起点和终点的编号及权重:" << endl;
	int x, y, w;    //x:起始点,y:终点,w:权重
	ArcNode* p, * q;
	for (int i = 1; i <= G.arcnum; i++) {
		cin >> x >> y >> w;
		p = (ArcNode*)malloc(sizeof(ArcNode)); //创建一个用于存放当前边的结点p
		p->nextarc = NULL;
		p->adjvex = y;
		p->weight = w;
		q = G.VNode[x].firstarc;
		//将边按顺序插入到链表末尾
		if (q == NULL) {
			G.VNode[x].firstarc = p;
		}
		else {
			while (q->nextarc != NULL) {
				q = q->nextarc;
			}
			q->nextarc = p;
		}
		p = (ArcNode*)malloc(sizeof(ArcNode));
		p->nextarc = NULL;
		p->adjvex = x;
		p->weight = w;
		q = G.VNode[y].firstarc;
		if (q == NULL) {
			G.VNode[y].firstarc = p;
		}
		else {
			while (q->nextarc != NULL) {
				q = q->nextarc;
			}
			q->nextarc = p;
		}
	}
}

void Dijkstra(Graph G, int v) //从顶点v出发求到其余顶点的最短路径
{
	for (int i = 1; i <= G.vexnum; i++) {  //初始化
		Adjvex[i] = v;
		Lowcost[i] = Infinity;
		Flag[i] = 0;
	}
	Lowcost[v] = 0;          //初始点到初始点的距离为0
	Flag[v] = 1;             //标注初始点
	int num = 1;             //记录目前被选中的顶点数目
	ArcNode* p;
	while (num < G.vexnum) {
		p = G.VNode[v].firstarc; //p为新选中的点的第一个邻接点
		while (p != NULL) {      //由于新点加入,更新起始点与其余未被选中的顶点之间的路径距离
			if (!Flag[p->adjvex] && Lowcost[p->adjvex] > Lowcost[v] + p->weight) {
				Lowcost[p->adjvex] = Lowcost[v] + p->weight;
				Adjvex[p->adjvex] = v;
			}
			p = p->nextarc;
		}
		int min = Infinity;
		for (int i = 1; i <= G.vexnum; i++) {  //从未选择的顶点中找下一个与源点距离最近的顶点
			if (!Flag[i] && Lowcost[i] < min) {
				min = Lowcost[i];
				v = i;               //更新v为目前与源点距离最近的顶点
			}
		}
		Flag[v] = 1;             //标记新选中的点
		num++;                   //目前被选中的顶点数目+1
	}
}

int main()
{
	Graph G;
	Create_Graph(G);    //创建图
	int v;
	cout << "请输入起始点:";
	cin >> v;
	Dijkstra(G, v);    //从顶点v出发求到其余顶点的最短路径
	for (int i = 1; i <= G.vexnum; i++) {
		if (i == v) {
			continue;
		}
		//输出起始点到各点的最短距离
		cout << "v" << v << " 到 " << "v" << i << " 的最短距离为" << Lowcost[i] << "  ";
		//输出起始点到各点的最短路径
		cout << "路径为:" << "v" << v;
		int j = i;
		vectorPath;
		while (j != v) {
			Path.push_back(j);
			j = Adjvex[j];
		}
		for (int k = Path.size() - 1; k >= 0; k--) {
			cout << "->v" << Path[k];
		}
		cout << endl;
	}
	return 0;
}

 运行结果:

顶点的个数:5
请输入边的个数:6
请输入各边起点和终点的编号及权重:
1 3 8
1 4 2
2 4 4
2 5 7
3 5 3
4 5 1
请输入起始点:3
v3 到 v1 的最短距离为6  路径为:v3->v5->v4->v1
v3 到 v2 的最短距离为8  路径为:v3->v5->v4->v2
v3 到 v4 的最短距离为4  路径为:v3->v5->v4
v3 到 v5 的最短距离为3  路径为:v3->v5

总结:普里姆算法(不知道的小伙伴可以看我之前的文章)类似,迪杰斯特拉算法的核心部分是一个双重循环。第一层循环是对所有的顶点;第二层有两个循环,一个是遍历邻接点更新数组 ,另一个是寻找 Lowcost 中的最小值。因此,迪杰斯特拉算法的时间复杂度为O(n^2),n 表示图中顶点的个数。这里,我采用了邻接表的结构来实现程序,换成邻接矩阵,方法也是基本相同的,且无论采用哪种方式表示图,该算法的时间复杂度均为O(n^2)。

以上便是我个人的学习成果,很高兴能与大家分享。

你可能感兴趣的:(算法思考,数据结构,算法,数据结构,c++)