数据结构与算法--图

概述

线性表的每个元素有线性关系,每个数据元素只有一个直接前去和一个直接后继。树的数据元素之间有着明细那的层次关系,并且每层上的数据元素可能和下一层中多个元素相关,但只能和上一层中一个元素相关。这和一对父母可以有很多孩子,但每个孩子却只能有一对父母是一个道理。可现实中,人与人之间关系复杂,不是简单一对一,一对多的关系。这种复杂关系更适合用图来表示。在图结构中,节点之间的关系可以是任意的,图中任意两个数据元素之间都可能相关。如下图所示: 
《数据结构与算法--图》 
无向边:Edge (vi,vj) 
有向边:也叫弧,Arc。 <vi,vj> 
对于无向图G=(V,E),如果边(v,v)E,则称定点v和v’互为邻接点(Adjacent),即v和v’相临接。边(v,v’)依附于顶点v,v’,或者说(v,v’)与定点v,v’相关联.定点v的度(Degree)是和v相关联的变的数目,记为TD(v). 
blabla…..我去,概念太多了,不写了,自己查书《大话数据结构》

图的存储结构

邻接矩阵

图的邻接矩阵(Adjacency Matrix)春初方式是用两个数组来表示图。一个以为数组存储定点,第二个存贮边或弧的信息 

f(x)={1,0,if(vi,vj)Eor<vi,vj>∈Eother

《数据结构与算法--图》 

上图是无向图邻接矩阵

 

《数据结构与算法--图》 
上图是有向图邻接矩阵 
《数据结构与算法--图》 
上图是有向网络邻接矩阵

邻接表

对于边相对与定点较少的图,邻接矩阵表示很浪费空间 
《数据结构与算法--图》 
稀疏边的邻接矩阵 
我们把一种数组与链表想结合的存储方法称为邻接表(Adjacency List). 
邻接表是这样子的:

  1. 图中顶点用一个一维数组存储,数组元素还需要存储指向第一个邻接点(vi,vj有边链接,vi,vj就互为邻接点)的指针,一边查询该定点的边信息。
  2. 图中每个顶点vi的所有邻接点构成一个线性表。

《数据结构与算法--图》
上图是无向图的邻接表 
《数据结构与算法--图》 
一个有向图的逆邻接表,就是对每个顶点vi都建议一个链接为vi为弧头的表。

十字链表

重新定义顶点表结点结构:

datafirstinfirstout

其中firstin表示入边表头指针,指向该顶点的入边表中第一个节点。firstout表示出边表头指针,指向该顶点的出边表中的第一个节点。 
重定义边表结点结构

tailvexheadvexheadlinktaillink

其中tailvex是指向弧起点在顶点表的下标,headvex是指弧终点在顶点表中的下标。headlink是指入边表指针域,指向终点相同的下一条边,taillink是指出边表指针域,指向起点相同的下一条边。如果是网,还可以增加一个weight域来存储权值。 
如下图,顶点依然存入一个一维数组{v0,v1,v2,v3},实线箭头指针的图示完全与上面有向图邻接表相同。就以顶点v0来说,firstout指向的是出边表中的第一个节点v3.所以v0边表节点的headvex=3,而tailvex其实就是当前顶点v0的下标,由于v0只有一个出边顶点,所以headlink和taillink都是空。 
《数据结构与算法--图》
十字链表 
我们重点需要解释虚线箭头的含义,它其实就是此图的逆邻接表的表示。对于v0来说,它有两个顶点v1和v2的入边。因此v0的firstin指向顶点v1的边表节点中headvex为0的节点,如上图中的 ①。接着由入边节点的headlink指向下一个边顶点v2,如上图的②。对于顶点v1,它有一个入边顶点v2,所以它的firstin指向顶点v2的边表节点中headvex为1的节点,如图中的③。顶点v2,v3也是同样有一个入边顶点,如图中④和⑤。
十字链表的好处是因为吧邻接表和逆邻接表整合在了一起,这样容易找到以vi为尾的弧,也容易找到以vi为头的弧,因而容易求得顶点的出度和入度。而且它除了结构复杂一点外,其实创建图算法的时间复杂度和邻接表示相同的。

邻接多重表

如果我们关注的重点是顶点,那么上面的邻接表是不错的存储结构。但如果我们关注边的操作,比如对边删除等操作,就意味着,我们需要找到这条边的两个边表节点进行操作。这其实还是比较麻烦的。比如下图中,如果要删除左图的(v0,v2)这条边,需要对邻接表结构中右边表的阴影两个结点进行删除操作《数据结构与算法--图》
于是我们对便表接待你的结构进行一些改造,也许可以避免刚才的问题。

ivexilinkjvexjlink

其中ivex和jvex是与某条边衣服的两个顶点再顶点表中下表。ilink指向依附顶点ivex的下一条边,jlink指向依附顶点jvex的下一条边。这就是邻接多重表结构。 
我们来看看结构示意图的绘制过程,理解了它是怎么链接的,就理解了多重表构造原理了。如下图,作图告诉我们他有四个顶点和5条边,显然,我们就应该先将4个顶点和五条边的边表节点画出来。由于是无向图,所以ivex是0,jvex是1还是翻过来都是无所谓的,不过为了绘图方便,都将ivex值设置得与一旁的顶点下标相同。 
《数据结构与算法--图》
多重链接表绘图1 
我们开始连线,如下图。首先连线的①②③④就是将顶点的firstedge指向一条边,顶点下标要与ivex的值相同,这样很好理解。接着,由于顶点v0的(v0,v1)边的邻边有(v0,v3)和(v0,v2)。因此⑤⑥的连线就是满足指向下一条依附于顶点v0的边的目标,注意ilink指向的结点的jvex一定要和它本身的ivex的值相同。同样的道理,连线⑦就是指(v1,v0)这条边,它是相当于顶点v1指向(v1,v2)边后的下一条。v2有三条边依附,所以在③之后就有了⑧⑨。连线⑩的就是顶点v3在连线④之后的下一条边。左图一共有五条边,所以右图有10条连线,完全符合预期。 
《数据结构与算法--图》
邻接多重表的绘图–连线 
到这里大家应该看出来了,邻接多重表与邻接表的差别,仅仅是在于同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个节点。这样对边的操作就方便多了。如果要删除上图左边(v0,v2)这条边,只需要将右图的⑥⑨的链接指向改为^即可。

边集数组

一图胜千言: 
《数据结构与算法--图》

图的遍历

深度优先

深度优先(Depth First Search) 
如下图,从顶点A开始要走遍所有的图顶点并作上标记,不重复不遗漏。 
《数据结构与算法--图》
有这样一个策略,我们从A开始,坐上标记后,前面有两条路,分别是B和F,我们给自己定一个原则,在没有碰到重复顶点的情况下,始终是向右手边走(假设我们在图中这样迷宫里),于是走到了B。整个过程,可以参见上面的右图,我们到达B节点,发现三条分支,分别通向顶点C,I,G,右手通行原则,使得我们走到了C顶点,。就这样,我们一直顺着右手通道走,一直走到了F顶点。当我们依然选择右手通道走过去后,发现走回到顶点了,因为我们做了标记,知道已经走过。此时我们退回到顶点F,走向从右数的第二条通道,到了G顶点,他有三条通道,发现B和D都已经是走过的,于是走到H,当我们面对通向H的两条通道D和E时,会发现已经走过了。此时还有很多分支没走,我们原路返回,继续寻找没有访问过的结点。直到返回顶点A,确认已经完成遍历任务,找到所有的九个节点。 
其实,就是上面右图那棵树的前序遍历。 
邻接矩阵的DFS代码:

//代码没有运行过,纯粹为了记笔记用,如果疏漏,欢迎指正 typdef int Boolean; Boolean visited[MAX]; //邻接矩阵的深度优先递归算法 void DFS(MGraph,int i){ int j; visited[i] = TRUE; printf("%c",G.vexs[i]); for(j = 0;j < G.numVertexes;j++){ //下一条边存在,且没有被访问,就向下搜索。 if(G.arc[i][j]==1 && !visited[j]) DFS(G,j); } } //邻接矩阵的深度遍历操作 void DFSTraverse(MGraph G){ int i; for(i = 0;i < G.numVertexes;i++){ visited[i] = FALSE; } for(i = 0;i < G.numVertexes; i++){ if(!visited[i]) DFS(G,i) } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

图是邻接表结构:其中DFSTraverse函数的代码几乎是相同的,知识在递归函数中因为将数组换成了链表而有不同,代码如下。

void DFS(GraphAdjList GL,int i){ EdgeNode * p; visited[i] = TRUE; printf("%c",GL->adjList[i].data); p = GL->adjList[i].firstedge; while(p){ if(!visited[p->adjvex]) DFS(GL,p->adjvex); p = p->next; } } //邻接表的深度遍历操作 void DFSTraverse(GraphAdjList GL){ int i; for(i = 0;i<GL->numVertexes;i++) visited[i] = FALSE; for(i = 0;i<GL->numVertexes;i++){ if(!visited[i]) DFS(GL,i); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

广度优先遍历

Breadth First Search,有点类似树的层序遍历。见下图: 
《数据结构与算法--图》

直接上代码: 
邻接矩阵的BFS代码

void BFSTraverse(MGraph G){
    int i,j; Queue Q; for (i=0;i < G.numVertexes;i++){ visited[i] = FALSE; } InitQueue(&Q); for(i=0;i<G.numVertexes;i++){ if(!visited[i]){ visited[i] = TRUE; printf("%c",G.vexs[i]); EnQueue(&Q,i); while(!QueueEmpty(Q)){ DeQueue(&Q,&i); for(j=0;j < G.numVertexes;j++){ if(G.arc[i][j] == 1 && !visited[j]){ visited[j] = TRUE; printf("%c",G.vexs[j]); EnQueue(&Q,j); } } } } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

邻接表的BFS算法

void BFSTraverse(GraphAdjList GL){ int i; EdgeNode * p; Queue Q; for (i = 0;i < GL->numVertexes; i++) visited[i] = FALSE; InitQueue(&Q); for(i=0;i < GL->numVertexes; i++){ visited[i] = TRUE; printf("%c",GL->adjList[i].data); EnQueue(&Q,i); while(!QueueEmpty(Q)){ DeQueue(&Q,&i); p = GL->adjList[i].firstedge; while(p){ if (!visited[p->adjvex]){ visited[p->adjvex] = TRUE; printf("%c",GL->adjList[p->adjvex].data); EnQueue(&Q,p->adjvex); } p = p->next; } } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

最小生成树

一个图的生成树,定义为包含图中全部的顶点,但只有足以构成一棵树的n-1条边(有n个结点)。我们把构造连通网的最小代价生成树称为最小生成树(Minimum Cost Spanning Tree)。

普里姆(Prim)算法

先构造如下图的邻接矩阵: 
《数据结构与算法--图》
现在,我们已经有了一个存储结构为MGrapgh的G。 
于是Prim算法代码如下:



typedef char VertexType; typedef int EdgeType; #define MAXVEX 100 #define INFINITY 65535 typedef struct{ VertexType vexs[MAXVEX]; EdgeType arc[MAXVEX][MAXVEX]; int numVertexes, numEdges; }MGraph; int a[9][9] = { { 0, 1, 5,INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY }, { 1, 0, 3, 7, 5, INFINITY, INFINITY, INFINITY ,INFINITY }, { 5, 3, 0, INFINITY, 1, 7, INFINITY, INFINITY, INFINITY }, { INFINITY, 7, INFINITY, 0, 2, INFINITY, 3, INFINITY, INFINITY }, { INFINITY, 5, 1, 2, 0, 3, 6, 9, INFINITY }, { INFINITY, INFINITY, 7, INFINITY, 3, 0, INFINITY, 5, INFINITY }, {INFINITY, INFINITY, INFINITY, 3, 6, INFINITY, 0, 2, 7}, { INFINITY, INFINITY, INFINITY, INFINITY ,9,5,2,0,4}, { INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY ,7,4,0} }; void CreateMGraph(MGraph *G){ for (int i = 0; i < 8; i++){ G->vexs[i] = '0' + i; } G->numEdges = 16; G->numVertexes = 9; for (int i = 0; i < G->numVertexes; i++){ for (int j = 0; j < G->numVertexes; j++) G->arc[i][j] = a[i][j]; } } void MiniSpanTree_Prim(MGraph *G){ int min, i, j, k; //保存相关顶点下标 int adjvex[MAXVEX]; /*保存相关顶点间边的权值*/ int lowcost[MAXVEX]; lowcost[0] = 0; //初始化第一个权值为0,即v0加入生成树 adjvex[0] = 0; //初始化第一个顶点下标 //将v0以外的值放入lowcost for (int i = 0; i < G->numVertexes; i++){ lowcost[i] = G->arc[0][i]; adjvex[i] = 0; } //每个顶点依次加入生成树 for (int i = 1; i < G->numVertexes; i++){ min = INFINITY; j = 1; k = 0; //找到一个lowcost的最小值 while (j < G->numVertexes){ if (lowcost[j] != 0 &&lowcost[j] < min ){ min = lowcost[j]; k = j; } j++; } //得到最小距离,以及对应的下标k lowcost[k] = 0;//表示顶点k加入生成树合集 printf("(%d,%d)\n", adjvex[k], k);//adjvex[k]是与顶点k相连的顶点 //更新所有顶点到生成树顶点的距离。当有顶点到达生成树中多个顶点时候,只记录最近的一个 //也就是其它节点到生成树中最近的那个结点的距离 for (j = 1; j < G->numVertexes; j++){ //找最近的那个节点 if (lowcost[j] != 0 && G->arc[k][j] < lowcost[j]){ lowcost[j] = G->arc[k][j]; adjvex[j] = k; } } } } void main(void){ MGraph g; CreateMGraph(&g); MiniSpanTree_Prim(&g); while(1); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

最后一个for循环: 
最后这个for循环是,找到顶点k,k成功加入到最小生成树顶点合集中(lowcost对应位置0)。然后,我们寻找其它顶点中, 离这个顶点合集最短的一个顶点。 
由于k是新加入合集的,lowcost中还没有记录其它顶点到K的距离,所以我们更新lowcost,将其它顶点到k的距离添加进去。遇到集合之外的顶点同时到达集合内多个顶点的,我们选择最小的距离,并且在adjvex数组中记录下j的最近距离lowcost[j]是指向哪一个节点的(比如adjvex[j]=k,表示顶点j到集合内最短距离边为(j,k),最短距离为lowcost[j]。)这里为什么要记录最短距离呢?因为我们最终求的是集合外一个结点到集合内任意结点的最短距离,所以,当一个结点同时和最小生成树结点集合中多个结点链接时候,我们只保存最小的值,较大的距离值就被抛弃了,反正后面比较的时候较大的值也会被抛弃。 
lowcost矩阵存储的都是集合外部节点到生成树节点集合中最近的距离。到底离哪个最近,则对应adjvex[j] = k,表示外部结点j离集合内部节点k最近。

总结一下Prim算法,假设N=(V,E)是连通网,TE是N上最小生成树中边的集合。算法从U=u0(u0V),TE={}开始。重复执行下述操作:在所有uU,vVU的边(u,v)E找一条代价最小的边(u0,v0)并入集合TE,同时v0并入U,直到U=V为止。此时TE中必有n-1条边,则T=(v,TE)为N的最小生成树。算法时间复杂度是O(n2)。具体参见算法导论的分析。

库鲁斯卡儿(Kruskal)

Prim算法是以某顶点为起点,逐步找各顶点最小权值的边来构建。但我们也可以直接以边为目标去构建,因为权值是在边上的,直接找最小权值的边来构建生成树也是很自然的,只不过构建时要考虑是否形成环路而已。此时我们就用到了图的存储结构中的边集数组结构。一下是edge边集数组结构的定义代码:

typedef struct{ int begin; int end; int weight; }
  • 1
  • 2
  • 3
  • 4
  • 5

我们将图的邻接矩阵通过程序转化为下图的右图的边集数组,并且对它们按照权值从小到大排序。 
《数据结构与算法--图》
Kruskal代码

void MiniSpanTree_Kruskal(MGraph G){ int i,n,m; //定义边集数组 Edge edges[MAXEDGE]; //定义一组用来判断边与边是否行成环路 int parent[MAXEDGE]; /*此处省略邻接矩阵G转化为边集数组edges并按照权由小到大排序的代码*/ for(i = 0;i < G.numVertexes; i++) parent[i] = 0;//初始化数组值为0 for(i = 0;i < G.numEdges;i++){ n = Find(parent,edges[i].begin); m = Find(parent,edges[i].end); if(n!=m){ parent[n] = m; printf("(%d,%d) %d",edges[i].begin,edges[i].end,edges[i].weight); /*将此边的结尾顶点法国如下标为起点的parent中*/ /*表示此顶点已经在生成树中*/ } } } //返回这个连通分量的最后一个顶点的下标 int Find(int * parent,int f){ while(parent[f] > 0){ f = parent[f]; } return f; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

同一个连通分量中,不管从哪一个vex开始,由begin->end不断“行走”,最后终点都是相同的,由此判断是否在同一连通分量。在边集数据中选择代价最小的边(已经排好序,一次遍历就OK),若该边依附的顶点落在T中不同的连通分量上,则将此边加入到结果集parent中,并且还合并了两个连通分量。

最短路径

迪杰斯特拉(Dijkstra)算法

这是一个按路径长度递增的次序产生最短路径的算法。 
如下图 
《数据结构与算法--图》 
v0v1的最短距离,很明显就是v0直接连接到v1。由于v1还与v2,v3,v4连线,所以此时我们同时求得了v0->v1->v2 = 1+3 =4;v0->v1->v3 = 1+7 =8;v0->v1->v4 = 1+5 = 6; 
现在,问题来了,从v0->v2的最短距离是多少?很明显不会是v0->v2的直接连线,而是v0->v1->v2 = 1+ 3 =4。由于V2还和v4 ,v5连线,所以此时我们同时得到了v0->v2->v4=4+1=5,v0->v2->v5=4+7=11.这里v0->v2我们用的是刚才计算出来的最短距离4。此时我们发现v0->v1->v2->v4=5比v0->v1->v4=6小。所以v0->v4最小的距离是5。 
当我们求v0->v3的最短距离时,通向v3的三条边,除了v6外,v0->v1->v3=8;v0->v4->v3=7;因此v0->v3的最短距离是7. 
好了,这就是大致Dijstra算法的基本步骤。它并不是一下子就求出了v0->8的最短路径,而是一步步求出它们之间顶点的最短路径,过程中都是基于已经求出的最短路径的基础上,求得最远顶点的最短路径,最终得到你想要的结果。


typedef char VertexType; typedef int EdgeType; #define MAXVEX 100 #define INFINITY 65535 typedef struct{ VertexType vexs[MAXVEX]; EdgeType arc[MAXVEX][MAXVEX]; int numVertexes, numEdges; }MGraph; int a[9][9] = { { 0, 1, 5,INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY }, { 1, 0, 3, 7, 5, INFINITY, INFINITY, INFINITY ,INFINITY }, { 5, 3, 0, INFINITY, 1, 7, INFINITY, INFINITY, INFINITY }, { INFINITY, 7, INFINITY, 0, 2, INFINITY, 3, INFINITY, INFINITY }, { INFINITY, 5, 1, 2, 0, 3, 6, 9, INFINITY }, { INFINITY, INFINITY, 7, INFINITY, 3, 0, INFINITY, 5, INFINITY }, {INFINITY, INFINITY, INFINITY, 3, 6, INFINITY, 0, 2, 7}, { INFINITY, INFINITY, INFINITY, INFINITY ,9,5,2,0,4}, { INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY ,7,4,0} }; void CreateMGraph(MGraph *G){ for (int i = 0; i < 8; i++){ G->vexs[i] = '0' + i; } G->numEdges = 16; G->numVertexes = 9; for (int i = 0; i < G->numVertexes; i++){ for (int j = 0; j < G->numVertexes; j++) G->arc[i][j] = a[i][j]; } } typedef int Patharc[MAXVEX];//用来存储最短路径的下标 typedef int ShortPathTable[MAXVEX];//用于存储到各个点最短路径的权值和,Dijstra, /*Dijstra算法,求有向网G的v0顶点到其余顶点v最短路径P[v]以及带权长度D[v]*/ /*P[v]的值为前驱顶点下标,D[v]表示v0到v的最短路径长度*/ void ShortestPath_Dijkstra(MGraph G, int v0, Patharc * P,ShortPathTable* D ){ int v, w, k, min; int final[MAXVEX];/*final[w]=1表示求得顶点v0到v_w的最短路径*/ for (v = 0; v < G.numVertexes; v++){/*初始化数据*/ final[v] = 0; /*全部顶点初始化为未知最短路径状态*/ (*D)[v] = G.arc[0][v]; /*将与v0点有连线的顶点加上权值*/ (*P)[v] = 0; /*初始化路径数组P为0*/ } (*D)[0] = 0; /*v0-v0路径为0*/ final[0] = 1; /*v0-v0不需要路径*/ /*开始主循环,每次求得v0到某个v顶点的最短路径*/ for (v = 1; v < G.numVertexes; v++){ min = INFINITY; for (w = 0; w < G.numVertexes; w++){ if (!final[w] && (*D)[w] < min){ k = w; min = (*D)[w];/*w顶点离v0顶点更近*/ } } final[k] = 1; /*将目前找到的最近的顶点置为1*/ for (w = 0; w < G.numVertexes; w++){ if (!final[w] && (min + G.arc[k][w] < (*D)[w])){ /*说明找到了更短的路径,修改D[w]和P[w]*/ (*D)[w] = min + G.arc[k][w]; /*修改当前路径长度*/ (*P)[w] = k; } } } } void main(void){ MGraph g; CreateMGraph(&g); ShortPathTable d; Patharc p; ShortestPath_Dijkstra(g,0,&p,&d); while (1); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

上面Dijstra和Prim代码很像,相似处就不说了,它们的区别如下: 
1. 前者是求最短路径,后者是求最小生成树 
2. 都有一步更新过程前者是 min+G.arc[k][w] < D[w]来判断,也就是新的最短路径+其它任意结点w的值是不是小于以前v0->w的路径长度D[w].是就更新v0到w的路径长度.P[w]记录下w的前驱k。 
后者是G.arc[k][j] < lowcost[j]来判断,也就是生成树加入了新成员k,然后其它结点到最小生成树结点集合的最近距离会改变,但有可能改变的只是与k相关的最近距离。我们遍历与k相关 
的其它节点的权值,然后这一判断其它结点到K的权值是不是小于以前到最小生成树合集的最近距离。是就更新这个结点到最小生成树的距离,并且在adjvex中记录下这个结点的前驱k。 
总结Dijkstra: 
Dijkstra算法解决了从某个源点到其余各顶点的最短路径问题。从循环嵌套中可知算法时间复杂度为O(n2)。可是我们如果需要知道每个顶点到其余节点的最短路径怎么办?最简单办法就是将每个结点选为源点运行一次DIjkstra算法。此时时间复杂度为O(n3)。但下面的Floyd算法,时间复杂度一样,但是代码特别简介。值得一学!

弗洛伊德(Floyd)算法

{未完待续}

   

    原文作者:天涯海角路
    原文地址: https://www.cnblogs.com/aademeng/articles/7530810.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞