《数据结构与算法分析》最短路径算法--BFS遍历和Dijkstra算法

前言:

      这一周学习的效率不错,不过图论这一块确实挺麻烦的,学完了算法之后,实现起来还需要码上不少代码。不过让我感到惊奇的一点是,我本科学习数据结构的时候,觉得Dijkstra算法很难,当时只能勉强记住算法的操作步骤,具体原理没弄明白。结果现在实现完BFS遍历之后,Dijkstra算法的雏形自动在我脑子里出来了,只是在一些细节上,算法复杂度上没有想清楚。然后看书上的概念,发现Dijkstra算法原来这么简单安静。修改一下BFS算法,三两下就实现出来了。果然是我的算法水平大大提升了吗。

我的github:

我实现的代码全部贴在我的github中,欢迎大家去参观。

https://github.com/YinWenAtBIT

最短路径算法:

无权最短路径:

一、定义:

在一个有向无权图中,使用某个顶点s作为出发点,找出从s到其他所有顶点的最短路径,我们只计算路径上的边数。这是有权最短路径问题的特殊情况,我们可以假设所有边上的权值为1。

二、BFS算法:

首先建立一个无权图,如下图所示,该图与《数据结构与算法分析》书中P221页用来举例的图相同。

1.选取一个点作为起点,在这里选取v3。

2.立刻可以得出从起点到v3的路径长为0,记录该信息

3.考察与s距离为1的顶点,可以得到v1,v6,记录下来。

4.考察距离为2的顶点,然后重复这个过程,直到所有可以到达的顶点都已经被考察过。


《数据结构与算法分析》最短路径算法--BFS遍历和Dijkstra算法_第1张图片

三、编码实现:

数据结构:

为了实现这个过程,我们需哟记录三个信息,顶点是否已经探测过,使用known记录,顶点距离起点的距离,用dv记录,到达该顶点的上一级顶点。如下表所示,其中每一步各个顶点所对应的信息变化图如下。

《数据结构与算法分析》最短路径算法--BFS遍历和Dijkstra算法_第2张图片

算法步骤:

1.初始化表结构数组,数组元素个数为顶点的个数。最开始所有的known元素置为false,距离置为无穷大,前路径置为NotAVertex。

2.将初始顶点距离置为0,然后入队。

3.出队,将出队顶点置为已知,将与它相邻的未知的顶点的距离设为该顶点的距离加一,路径设为该顶点,然后相邻的顶点入队。

4.重复这个过程,直到队列为0。

使用队列将使得算法的复杂度为O(V+E),已经达到了最优。

实现代码:

路径表数据结构定义:

struct TableNode;
typedef TableNode* Table;

struct TableNode
{
	bool known;
	int dist;
	Index path;
};

初始化路径表:

/*最短无权路径,初始化路径表*/
void initTable(Index V, int num, Table T)
{
	for(int i=0; i<num; i++)
	{
		T[i].known = false;
		T[i].dist = Infinity;
		T[i].path = -1;
	}
	T[V].dist = 0;
}
使用BFS算法计算路径:

这里使用图是使用邻接表表示的图,队列函数时在第三章中自行实现的队列。

/*最短无权路径,计算路径*/
void unweighted(Index S, Table T, Graph G)
{
	Queue Q = createQueue();
	enqueue(S, Q);
	Index V,W;


	while(!isEmpty(Q))
	{
		V = dequeue(Q);
		T[V].known = true;

		Edge edge = G->TheCells[V].next;
		while(edge !=NULL)
		{
			W = edge->vertexIndex;
			if(T[W].dist == Infinity)
			{
				T[W].dist = T[V].dist+1;
				T[W].path = V;
				enqueue(W,Q);
			}
			edge = edge->next;
		}
	}
	disposeQueue(Q);
}

/*最短无权路径*/
Table UnweightedShortestPath(VertexType vertex, Graph G)
{
	Index S = findVertex(vertex, G);
	if(G->TheCells[S].Info != Legitimate)
	{
		fprintf(stderr, "vertex %s does not exist", vertex);
		return NULL;
	}

	/*生成列表*/
	Table T = (Table)calloc(G->vertex, sizeof(TableNode));
	if(T == NULL)
	{
		fprintf(stderr, "not enough memory");
		return NULL;
	}

	initTable(S, G->vertex, T);
	
	unweighted(S, T, G);
	return T;
}
测试结果:

首先输入上面给出的图,输入的路径权值不为0,因为后面使用Dijkstra算法时会使用该图,在这里BFS算法默认路径权值为1。
《数据结构与算法分析》最短路径算法--BFS遍历和Dijkstra算法_第3张图片

输入起始顶点v3,调用BFS算法得到最短路径表,然后输出最短路径表:

图中两个顶点之间的数字代表路径长度,长度超过0的路径写出了路径的每一条边。

《数据结构与算法分析》最短路径算法--BFS遍历和Dijkstra算法_第4张图片

Dijkstra算法:

定义:

在这里的图的边是带有权值的,现在要寻找从起点S到图中任意一点的最短路径。

二、Dijkstra算法:

首先建立一个带权有向图,如下图所示,该图与无权路径图相同,只不过是路径上带有了权值。

1.选取一个点作为起点,在这里选取v1。

2.立刻可以得出从起点到v1的路径长为0,记录该信息

3.考察与v1相邻的所有顶点,并且记录下从v1到他们的距离

4.选择与v1距离最短的顶点v4,该顶点被选取之后,则v4到起点的距离被定下来了。然后考察该顶点的所哟相邻顶点,如果从该顶到相邻顶点的路径短于之前选择的路径,则更新路径。

5.选择余下未被确定顶点中有最短路径的那个,为v2,然后重复4过程,知道所有的顶点确定下最短距离。

《数据结构与算法分析》最短路径算法--BFS遍历和Dijkstra算法_第5张图片

三、编码实现:

数据结构:

为了实现这个过程,我们需哟记录三个信息,顶点是否已经确认最短距离,使用known记录,顶点距离起点的距离,用dv记录,到达该顶点的上一级顶点。如下表所示,其中每一步各个顶点所对应的信息变化图如下。

《数据结构与算法分析》最短路径算法--BFS遍历和Dijkstra算法_第6张图片

算法步骤:

1.初始化表结构数组,数组元素个数为顶点的个数。最开始所有的known元素置为false,距离置为无穷大,前路径置为NotAVertex。

2.将初始顶点距离置为0,然后插入最小堆。

3.获取堆的最小值,如果该值对应的顶点未确定,则确定该顶点,并且更新该顶点相邻的顶点的路径长度。被更新的顶点插入最小堆

4.重复这个过程,堆大小为0。

使用优先队列,将使得每次查找使用时间为O(logV),一共有V个点,进行V次查找,所以总的时间复杂度为O(E+V*LogV)。

实现代码:

路径表数据结构定义,初始化方式与上面相同。

使用Dijkstra算法计算路径:

首先初始化表,然后进行计算。在这里使用的优先队列为二叉堆,在第六章中实现,不过这里对二叉堆进行了稍微的修改,数据中保存了路径值和顶点序号。

/*最短有权路径,Dijkstra算法*/
Table Dijkstra(VertexType vertex, Graph G)
{
	Index S = findVertex(vertex, G);
	if(G->TheCells[S].Info != Legitimate)
	{
		fprintf(stderr, "vertex %s does not exist", vertex);
		return NULL;
	}

	/*生成列表*/
	Table T = (Table)calloc(G->vertex, sizeof(TableNode));
	if(T == NULL)
	{
		fprintf(stderr, "not enough memory");
		return NULL;
	}

	initTable(S, G->vertex, T);
	Dijkstra(S, T, G);
	return T;
}
/*最短有权路径,Dijkstra算法*/
void Dijkstra(Index S, Table T, Graph G)
{
	Index V,W;
	PriorityQueue H = Initialize(G->edge);

	struct AdjvexPath temp;
	temp.cost =0;
	temp.vertex = S;
	Insert(temp, H);

	while(!isEmpty(H))
	{		
		while(1)
		{
			temp = DeleteMin(H);
			V = temp.vertex;
			if(!T[V].known)
				break;

			if(isEmpty(H))
				break;
		}
		if(V == NotAVertex)
			break;

		T[V].known = true;

		Edge edge = G->TheCells[V].next;

		while(edge !=NULL)
		{
			W = edge->vertexIndex;
			if(!T[W].known)
			{
				if(T[W].dist > T[V].dist +edge->weight)
				{
					T[W].dist = T[V].dist +edge->weight;
					T[W].path = V;
					temp.cost = T[W].dist;
					temp.vertex = W;
					Insert(temp, H);
				}
			}

			edge = edge->next;
		}/*while*/
	}

	Destory(H);

}
测试结果:

首先输入图:

《数据结构与算法分析》最短路径算法--BFS遍历和Dijkstra算法_第7张图片
然后计算结果:

《数据结构与算法分析》最短路径算法--BFS遍历和Dijkstra算法_第8张图片

总结:

无论对于有权还是无权,使用一个记录下查找过程的表非常的重要,这一点需要牢记。这一章的编码实现中,使用了大量我之前实现过的数据结构。如果不是之前的数据结构都老老实实自己编码实现过。那么遇到图论这一章时,我必定是没法这么快理解整个过程,也没法做到半天就编码实现完成了。

所以说基础打扎实非常的重要。

另外一点就是看到的使用模板类的好处。这一章中使用队列,或者二叉堆时,我整个数据结构的逻辑没有改变,就是需要改变其中储存的数据,如果使用模板,并且重载了比较大小的运算符函数,那么必定实现的过程会更加的迅速了。


你可能感兴趣的:(《数据结构与算法分析》最短路径算法--BFS遍历和Dijkstra算法)