数据结构——第六讲、图(图、图的遍历、最短路径、最小生成树和拓扑排序)

6.1 什么是图

  图是描述多对多关系的结构,图包含顶点和边,顶点用V(Vertex)表示,边用E(Edge)表示,双向边用(v,w)圆括号括住的顶点对表示,单向边用<v,w>表示。
操作集:

Graph Create();
Graph InsertVertex(Graph G, Vertex V);
Graph InsertEdge(Graph G, Edge E);
void DFS(Graph G, Vertex V); //从V出发深度优先遍历图G
void BFS(Graph G, Vertex V); //从V出发广度优先遍历图G
void ShortestPath(Graph G, Vertex V, int Dist[]);//计算图G中顶点V到其他任意顶点的最短距离。
void MST(Graph G);//计算图G的最小生成树

怎么在程序中表示图:

邻接矩阵:
  对于一个图有N个节点,节点从0到N-1进行编号,用一个二维数组存储G[i,j],如果第i个元素和第j个元素之间有边,那么就把G[i,j]的值设置为1,否则设置为零。
  对于无向图(即两个节点之间的边没有方向,G[i,j]和G[j,i]等效),我们可以省略一半的存储空间,用一个(N*(N-1)/2)大小的一维数组来存储,要找i,j之间的边可以用(i*(i+1)/2+j)来索引。有向图不可省略。
  邻接矩阵查找某个节点邻接的节点很方便,只需扫描第i行的元素是否为1即可,对于有权图,把数组值改为权值即可,对于有向图,ij表示从i到j,ji表示从j到i。
  邻接矩阵存储稀疏图(节点很多边很少)会非常浪费空间,比较适合存稠密图。
邻接表:
  稀疏图用邻接矩阵会浪费很多空间,那么我们可以用邻接表来存储稀疏图,邻接表是一个链表类型的数组,有N个节点就有N个元素,每个元素的值都是一个链表头,这个链表链接着这个元素对应节点的所有的边,无所谓顺序 ,一个接一个把边存下来,邻接表存稀疏矩阵比较好,但是终究不如邻接矩阵方便。

6.2 图的遍历

  图的遍历有两种方式:DFS(深度优先搜索)和BFS(广度优先搜索)。

  • DFS:就是从某一个节点开始,依次访问它的邻接点,每个邻接点都递归的调用DFS方法。
void DFS(Vertex V){
	V.Visited = true;
	//对于邻接表来说,访问V的每一个邻接点就是找到V对应的链表依次访问
	//对于邻接矩阵来说,需要访问V对应的那一行里所有的非零(或无穷)项
	for(V的每一个邻接点W){
		if(W没有被访问过)DFS(W);}
}
  • BFS:广度优先搜索,类似于树的层序遍历,用队列实现。
void BFS(Vertex V){
	V.Visited = true;
	Queue Q;
	AddQ(Q,V);
	while(!IsEmpty(Q)){
		V = Delete(Q);
		for(V的每一个邻接点W){
			if(W没有被访问){
				V.Visited = true;
				AddQ(Q,W);}
	}
}

  两种遍历方法各有优劣,不同的情况适用不同的方法。
  有的时候图并不是联通的,这时候要遍历,需要把图中的每个没有被访问的节点都调用一次BFS或DFS,就可以把每个节点都访问到。(两种遍历方法是检索数据的方式,并不是说找不到这些数据了,而是按照某种特定方法来遍历,达到某些目的)。

7.1 最短路径问题

  最短路径问题分为:单源最短路径和多源最短路径。
单源最短路径:
  单源无权图的算法思想:从源点开始一圈一圈往外扩展,依次找到与源点距离为1的,与源点距离为2的节点,对广度优先搜索(BFS)稍作修改即可。

void Unweighted( Vertex S ){
	int Dest[N] = {-1};
	Vertex Path[];
	EnQueue( S,Q );
	Dest[S] = 0;
	while( !IsEmpty Q ){
		V = DelQueue(Q);
		for(V的每一个邻接点W){
			if( Dist[W] == -1){
				//到W的距离等于到V的距离加1
				Dist[W] = Dist[V]+1;
				//到达W的最短路径必须经过V
				Path[W] = V;
			}
		}
	}
}
  • 单源有权图的算法(dijkstra算法):有一个集合S,它里面收录了源点和已经找到最短路径的点,按照距离非递减的顺序依次把所有的点都收录到S里面。
//需要把Dist初始化为正无穷,Path初始化为-1
void Dijkstra(VerTex V){
	Dest[V] = 0;
	Collected[V] = true;
	for(V的每一个邻接点W)//E<V,W>bi表示V到W的距离,也就是权重。
		Dest[W] = E<V,W>;while(1){
		V = 还未收录的节点的Dest最小的节点。
		if(所有的节点都被收录)break;
		Collected[V] = true;
		for(V的每一个邻接点){
			if( Collected[W] == false ){
				if(Dist[V] + E<V,W> < Dist[W]){
					Dist[W] = Dist[V] + E<V,W>;
					Path[W] = V;
				}
			}
		}
	}
}

多源最短路径:
方法一:可以直接把单源最短路径的方法对每一个节点都调用一遍(对稀疏图效果好)。
方法二:Floyd算法

bool Floyd( MGraph Graph, WeightType D[][MaxVertexNum], Vertex path[][MaxVertexNum] )
{
    Vertex i, j, k;
    /* 初始化 */
    for ( i=0; i<Graph->Nv; i++ )
        for( j=0; j<Graph->Nv; j++ ) {
            D[i][j] = Graph->G[i][j];
            path[i][j] = -1;
        }
    for( k=0; k<Graph->Nv; k++ )
        for( i=0; i<Graph->Nv; i++ )
            for( j=0; j<Graph->Nv; j++ )
                if( D[i][k] + D[k][j] < D[i][j] ) {
                    D[i][j] = D[i][k] + D[k][j];
                    if ( i==j && D[i][j]<0 ) /* 若发现负值圈 */
                        return false; /* 不能正确解决,返回错误标记 */
                    path[i][j] = k;
                }
    return true; /* 算法执行完毕,返回正确标记 */
}

8.1 最小生成树问题

最小生成树: 一个图构成最小生成树,图里面的每一个联通的节点都必须包含在生成树里面,生成树的边最少即(N-1)条,不构成回路,而且选出的边的权重和必须最小。
有两种算法:Prim算法和KrusKal算法

  • Prim算法:从一个根节点开始,依次选出可以选的边,选择的边要是权重最小的,且不会构成回路。适用于边比节点多很多。
void prim(VerTex V){
	//根节点的parent就是-1,非根节点的parent是它的父元素。
	parent[V] = -1;
	//根节点的dest为0,根节点的邻接点dest为边的距离,其余的dest都是无穷大。
	dest[V] = 0;
	for(V的每一个邻接点W){
		dest[W] = E<V,W>;
	}
	while(1){
		V = 全部节点中dest最小的非零节点;
		if(没有这样的节点V)
			break;
		//将V收录进MST
		dest[V] = 0;
		for(V的每一个邻接点W){
			//如果W没有被收录,即W的dest不为零
			if(dest[W] != 0 && E<W,V> < dest[W]){
				dest[W] = E<W,V>;
				parent[W] = V;
			}
		}
	}
	if(MST中的节点不足|V|)
		Error("生成树不存在!")
}
  • KrusKal算法:每次选择权重最小的,不会构成回路的边。适用于边和节点属于同一数量级的
void kruskal(VerTex V){
	//MST初始为空。
	MST = {};
	while(MST中的边不到N-1&& 边集E中还有边存在){
		//最小堆
		//个人思路:构造一个存储边的结构体,包含这条边两端的节点信息
		从E中取出一条权重最小的边E<V,W>;
		将E<V,W>从边集中删除;
		//并查集检查选中边的两个节点是否在同一个集合中
		//个人思路:将边加入MST中时,同时将边的两个节点标记为已经加入,判断是否构成回路就判断这条边两个节点是否都已经加入。
		if(E<V,W>添加到MST中不会构成回路)
			将E<V,W>加入MST中
	}
	if(MST中的边不到N-1)
		Error("生成树不存在");
}

8.1 拓扑排序

  拓扑序: 如果图中从V到W有一条有向路径,则V一定排在W之前。满足此条件的顶点序列称为一个拓扑序。获得一个拓扑序的过程就是拓扑排序。AOV如果有合理的拓扑序,则必定是一个有向无环图(DAG)。

void topSort(){
	for(图中的每一个顶点V)
		if(Indegree[V] == 0)
			InQueue(Q,V)
	while(!IsEmpty(Q)){
		V = Dequeue(Q);
		输出V,或者记录V的输出序号。
		for(V的每一个邻接点W)
			if(--Indegree[W] == 0)
				InQueue(Q,W)
	}
	if(输出的个数不足|V|)
		Error("图中有回路");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值