前言:
这一周学习的效率不错,不过图论这一块确实挺麻烦的,学完了算法之后,实现起来还需要码上不少代码。不过让我感到惊奇的一点是,我本科学习数据结构的时候,觉得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的顶点,然后重复这个过程,直到所有可以到达的顶点都已经被考察过。
三、编码实现:
数据结构:
为了实现这个过程,我们需哟记录三个信息,顶点是否已经探测过,使用known记录,顶点距离起点的距离,用dv记录,到达该顶点的上一级顶点。如下表所示,其中每一步各个顶点所对应的信息变化图如下。
算法步骤:
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。
输入起始顶点v3,调用BFS算法得到最短路径表,然后输出最短路径表:
图中两个顶点之间的数字代表路径长度,长度超过0的路径写出了路径的每一条边。
Dijkstra算法:
定义:
在这里的图的边是带有权值的,现在要寻找从起点S到图中任意一点的最短路径。
二、Dijkstra算法:
首先建立一个带权有向图,如下图所示,该图与无权路径图相同,只不过是路径上带有了权值。
1.选取一个点作为起点,在这里选取v1。
2.立刻可以得出从起点到v1的路径长为0,记录该信息
3.考察与v1相邻的所有顶点,并且记录下从v1到他们的距离
4.选择与v1距离最短的顶点v4,该顶点被选取之后,则v4到起点的距离被定下来了。然后考察该顶点的所哟相邻顶点,如果从该顶到相邻顶点的路径短于之前选择的路径,则更新路径。
5.选择余下未被确定顶点中有最短路径的那个,为v2,然后重复4过程,知道所有的顶点确定下最短距离。
三、编码实现:
数据结构:
为了实现这个过程,我们需哟记录三个信息,顶点是否已经确认最短距离,使用known记录,顶点距离起点的距离,用dv记录,到达该顶点的上一级顶点。如下表所示,其中每一步各个顶点所对应的信息变化图如下。
算法步骤:
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); }测试结果:
首先输入图:
总结:
无论对于有权还是无权,使用一个记录下查找过程的表非常的重要,这一点需要牢记。这一章的编码实现中,使用了大量我之前实现过的数据结构。如果不是之前的数据结构都老老实实自己编码实现过。那么遇到图论这一章时,我必定是没法这么快理解整个过程,也没法做到半天就编码实现完成了。
所以说基础打扎实非常的重要。
另外一点就是看到的使用模板类的好处。这一章中使用队列,或者二叉堆时,我整个数据结构的逻辑没有改变,就是需要改变其中储存的数据,如果使用模板,并且重载了比较大小的运算符函数,那么必定实现的过程会更加的迅速了。