每周一道数据结构(一)图
图的定义
图是由结点的有穷集合V和边的集合E组成。其中,为了与树形结构加以区别,在图结构中常常将结点称为顶点,边是顶点的有序偶对,若两个顶点之间存在一条
边,就表示这两个顶点具有相邻关系。
- 图的遍历问题
- 最小生成树问题
- 单源最短路径问题
- 拓扑排序问题
- 关键路径
图的遍历方法
和树的遍历类似,图的遍历也是从某个顶点出发,沿着某条搜索路径对图中每个顶点各做一次且仅做一次访问。它是许多图的算法的基础。深度优先遍历和广度优先遍历是最为重要的两种遍历图的方法。它们对无向图和有向图均适用。
1.深度优先算法的思想:
假设图中任何一点都没有访问过,这时可以选择任意一点作为出发点,类似于数的前序遍历。则具体步骤如下:
首先访问顶点V,并标记为以访问过。从顶点V的所有邻接点中,搜索为访问过的点W,如果存在W,则把W当作上一步的V,重复上一过程。直到图中所有可到达的路径都被访问过停止。如果图中还有为放过的顶点,则继续冲这部分定点中,选择一个节点作为顶点重复此过程。
代码:
void DFSTraverse(ALGraph *G) { for(int i=0;i<G->n;i++) visited[i]=FALSE; //标志向量初始化 for(int i=0;i<G->n;i++) if(!visited[i]) //vi未访问过 DFS(G,i); //以vi为源点开始DFS搜索 }//DFSTraverse void DFS(ALGraph *G,int i) { visit(G->adjlist[i].vertex);//访问顶点vi visited[i]=TRUE; //标记vi已访问 p=G->adjlist[i].firstedge; //取vi边表的头指针 while(p){//依次搜索vi的邻接点vj,这里j=p->adjvex if (!visited[p->adjvex])//若vi尚未被访问 DFS(G,p->adjvex);//则以Vj为出发点向纵深搜索 p=p->next; //找vi的下一邻接点 } }//DFS
2.广度优先算法的思想:
设图G的初态是所有顶点均未访问过。在图中任选一顶点V,则广度优先遍历的具体步骤为:
首先访问顶点V,接着依次访问v的所有邻接点W1,W2,…,Wt,然后再依次访问与Wl,W2,…,Wt邻接的所有未曾访问过的顶点。依此类推,直至图中所有和顶点V有路径相通的顶点都已访问到为止。此时从V开始的搜索过程结束。
代码:
void BFS(ALGraph*G,int k) { CreateQueue Q; //须将队列定义中DataType改为int EdgeNode *p; InitQueue(&Q);//队列初始化 //访问源点vk visit(G->adjlist[k].vertex); visited[k]=TRUE; EnQueue(&Q,k);//vk已访问,将其人队。(实际上是将其序号人队) while(!QueueEmpty(&Q)){//队非空则执行 i=DeQueue(&Q); //相当于vi出队 p=G->adjlist[i].firstedge; //取vi的边表头指针 while(p){//依次搜索vi的邻接点vj(令p->adjvex=j) if(!visited[p->adivex]){ //若vj未访问过 visit(C->adjlistlp->adjvex].vertex); //访问vj visited[p->adjvex]=TRUE; EnQueue(&Q,p->adjvex);//访问过的vj人队 }//endif p=p->next;//找vi的下一邻接点 }//endwhile }//endwhile }//end of BFS
生成树和最小生成树
如果连通图G的一个子图是一棵包含G的所有顶点的树,则该子图称为G的生成树(SpanningTree)。生成树是连通图的包含图中的所有顶点的极小连通子图。图的生成树不惟一。从不同的顶点出发进行遍历,可以得到不同的生成树。
对于连通的带权图(连通网)G,其生成树也是带权的。生成树T各边的权值总和称为该树的权,记作:
这里:TE表示T的边集,w(u,v)表示边(u,v)的权。
权最小的生成树称为G的最小生成树(Minimum SpannirngTree)。最小生成树可简记为MST。
MST的性质:
设G=(V,E)是一个连通网络,U是顶点集V的一个真子集。若(u,v)是G中所有的一个端点在U(u∈U)里、另一个端点不在U(即v∈V-U)里的边中,具有最小权值的一条边,则一定存在G的一棵最小生成树包括此边(u,v)。
求MST的一般算法可描述为:
针对图G,从空树T开始,往集合T中逐条选择并加入n-1条安全边(u,v),最终生成一棵含n-1条边的MST。
当一条边(u,v)加入T时,必须保证T∪{(u,v)}仍是MST的子集,我们将这样的边称为T的安全边。
伪代码:
GenerieMST(G) {//求G的某棵MST T=NULL; //T初始为空,是指顶点集和边集均空 while( T未形成G的生成树 ){ 找出T的一条安全边(u,v);//即T∪{(u,v)}仍为MST的子集 T=T∪{(u,v)}; //加入安全边,扩充T } return T; //T为生成树且是G的一棵MST }
Prim算法与Kruskal算法:
Prim思想:
首先定义一个空集合T,用来存放MST的边和顶点。从图中选取一个顶点u,找到与u相连接的一条最短的边(u,v)作为第一条边,把(u,v)边和顶点v加入到集合T中。接下来,我们要选择下一条最短边(轻边),要求这条边的起点在集合T中,终点在集合T外。再重复上面的过程,直到找到n-1条边为止。
由于可能存在相同长度的轻边,这就导致了最小生成树不唯一。
Prim伪码:
PrimMST(G,T,r) { //求图G的以r为根的MST,结果放在T=(U,TE)中 InitCandidateSet(…);//初始化:设置初始的轻边候选集,并置T=({r},¢) for(k=0;k<n-1;k++){ //求T的n-1条树边 (u,v)=SelectLiShtEdge(…);//选取轻边(u,v); T←T∪{(u,v)};//扩充T,即(u,v)加入TE,点v并人集U ModifyCandidateSet(…); //根据点v调整候选轻边集 } }
复杂度为O(n^2),与边数无关.
Kruskal思想:
与Prim算法不同,它只考虑每次所选的边为剩下的边集合中最短的那条边。这样就导致了,在找到最后一条边之前,集合T始终是一个森林。
KruskalMST(G)
{//求连通网G的一棵MST T=(V,¢); //初始化,T是只含n个顶点不包含边的森林 依权值的递增序对E(G)中的边排序,并设结果在E[0..e-1]中 for(i=0;i<e;i++) { //e为图中边总数 取E[0..e-1)中的第i条边(u,v) if u和v分别属于T中两棵不同的树then T=T∪{(u,v)};//(u,v)是安全边,将其加入T中 if T已是一棵生成树then `` return T; }//endfor return T; }
复杂度0(elge),与边有关。
最短路径
单源最短路径问题:已知有向带权图(简称有向网)G=(V,E),找出从某个源点s∈V到V中其余各顶点的最短路径。
1、边上权值相等的有向网的单源最短路径
用求指定源点的BFS生成树的算法可解决。
2、迪杰斯特拉(Dijkstra)算法求单源最短路径
由Dijkstra提出的一种按路径长度递增序产生各顶点最短路径的算法。
Dijkstra思想:
设S为最短距离已确定的顶点集,V-S是最短距离尚未确定的顶点集。
①初始化
初始化时,只有源点s的最短距离是已知的(SD(s)=0),故点集S={s},点集V-S为空。
②重复以下工作,按路径长度递增次序产生各顶点最短路径
在当前点集V-S中选择一个最短距离最小的点来扩充点集S,以保证算法按路径长度递增的次序产生各顶点的最短路径。
当点集V-S中仅剩下最短距离为∞的点,或者所有点已扩充到点集S时,s到所有顶点的最短路径就求出来了。
注意:
①若从源点到某点的路径不存在,则可假设到该点的最短路径是一条长度为无穷大的虚拟路径。
②从源点s到终点v的最短路径简称为v的最短路径;s到v的最短路径长度简称为v的最短距离,并记为SD(v)。
Dijkstra(G,D,s){ //用Dijkstra算法求有向网G的源点s到各顶点的最短路径长度 //以下是初始化操作 S={s};D[s]=0; //设置初始的点集S及最短距离D for( all i∈ V-S )do //对点集V-S中每个顶点i D[i]=G[s][i]; //设置i初始的估计距离为w<s,i> //以下是扩充红点集 for(i=0;i<n-1;i++)do{//最多扩充n-1个点到点集S D[k]=min{D[i]:all i V-S}; //在当前点集V-S中选估计距离最小的顶点k if(D[k]等于∞) return; //点集V-S中所有点的估计距离均为∞时, //表示这些顶点的最短路径不存在。 S=S∪{k}; //将点扩充到点集S中 for( all j∈V-S )do //调整V-S中剩余点的估计距离 if(D[j]>D[k]+G[k][j]) //新点k使原D[j]值变小时,用新路径的长度修改D[j], //使j离s更近。 D[j]=D[k]+G[k][j]; } }
3、floyd算法
通过一个图的权值矩阵求出它的每两点间的最短路径矩阵。
floyd思想:
- 从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
- 对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比已知的路径更短。如果是更新它。
伪码:
void floyd(A,D) { //初始化 D[u,v]=A[u,v] //更新 For k:=1 to n For i:=1 to n For j:=1 to n If D[i,j]>D[i,k]+D[k,j] Then D[I,j]:=D[I,k]+D[k,j]; }
拓扑排序
对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若<u,v> ∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(TopoiSicai Order)的序列,简称拓扑序列。
拓扑排序方法分为两种:无前趋的顶点优先的拓扑排序方法和无后继的顶点优先拓扑排序方法。
1.无前趋的顶点优先的拓扑排序方法
该方法的每一步总是输出当前无前趋(即人度为零)的顶点。
伪码:
NonPreFirstTopSort(G) {//优先输出无前趋的顶点 while(G中有人度为0的顶点)do{ 从G中选择一个人度为0的顶点v且输出之; 从G中删去v及其所有出边; } if(输出的顶点数目<|V(G)|) //若此条件不成立,则表示所有顶点均已输出,排序成功。 Error("G中存在有向环,排序失败!"); }
2.无后继的顶点优先拓扑排序方法
该方法的每一步均是输出当前无后继(即出度为0)的顶点。对于一个DAG,按此方法输出的序列是逆拓扑次序。
伪码:
NonSuccFirstTopSort(G){//优先输出无后继的顶点 while(G中有出度为0的顶点)do { 从G中选一出度为0的顶点v且输出v; 从G中删去v及v的所有人边 } if(输出的顶点数目<|V(G)|) Error("G中存在有向环,排序失败!"); }
本文 由 cococo点点 创作,采用 知识共享 署名-非商业性使用-相同方式共享 3.0 中国大陆 许可协议进行许可。欢迎转载,请注明出处:
转载自:cococo点点 http://www.cnblogs.com/coder2012