图结构和相关问题
Posted hhhuang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图结构和相关问题相关的知识,希望对你有一定的参考价值。
图的定义:图由顶点和边组成,每条边的两端是图的两个顶点。记作G(V,E),V是顶点集,E 为边集。
一般图分为有向图和无向图。
顶点的度是指和该顶点相连的边的条数。特变的对于有向图,顶点的出边条数成为出度,顶点的蠕变条数成为入度。顶点和边都可以由一些属性,称为点权和边权。
图的存储:
图可以使用两种存储方式:邻接矩阵和邻接表。
邻接矩阵:适合顶点数目不多的稠密图。
设图G(V,E)的顶点编号为0-N-1,那么可以令二维数组G[N][N]的两维分别表示图的顶点标号,及如果G[i][j]的值为1,表示i和j之间有变。这个二维矩阵被称为邻接矩阵。并且如果存在边权可以让G[N][N]中存放边权。
无向图的邻接矩阵是对称矩阵。
邻接表:适合顶点个数较多的稀疏图。
设图G(V,E)的顶点标号为0,1,… ,N-1,每个顶点都有可能有若干条出边,如果把同一个顶点的所有出边放在一个列表中,那么N个顶点就会有N个列表(没有出边,对应空表)。这N个列表被称为图的邻接表。记作Adj[N]。
邻接表实现可以使用变长数组vector,开一个vector数组Adj[N], N是顶点个数,每个顶点对应一个变长数组,存储其出边。
如下所示:
<Vector<int> Adj[N];>
如果节点还有权值,我们可以定义一个结构体:
Struct Node{
?????Int v;
?????Int w;
};
然后vector邻接表中的元素类型就是Node型的。
<Vector<Node> Adj[N];>
图的遍历:
是指对图的所有顶点按一定顺序进行访问,遍历方法一般有两种:深度优先搜索(DFS)和广度优先搜索(BFS)。
深度优先搜索以“深度”作为第一关键词,每次都沿着路径到不能再前进时才退回到最近的岔路口。
DFS的具体实现,首先介绍两个概念:
连通分量。在无向图中,如果两个顶点之间可以相互到达(可以是通过一定路径间接到达),那么就称这两个顶点连通。如果图G(V,E)的任意两个顶点都连通,则通图G为连通图,否则称G为非连通图,且称其中的极大连通子图为连通分量。
强连通分量。在有向图中,如果俩ing个顶点可以各自通过一条有向路径到达另一个顶点,就称这两个顶点强连通。如果图G(V,E)的任意两个顶点都强连通,则称图G为强连通图;否则称G为非强连通图,且称其中的极大强连通子图为强连通分量。
如果要遍历一个图就要对所有的连通块进行遍历,如果已知的图是连通图,则只需要一次DFS遍历就可以完成。
DFS的伪代码:(可以使用临界矩阵和邻接表实现)
DFS(u){//访问顶点u
vis[u] = true; //设置u为已访问
for(从u出发能到达的所有顶点v){ //枚举从u出发可以到达的所有顶点v
if(vis[v] == false){
DFS(v);
}
}
DFSTrave(G){ //遍历图
for(G 的所有顶点u) //对G的所有顶点u
if vis[u] == false //如果u未被访问
DFS(u); //访问u所在的连通块
}
广度优先搜索(BFS)遍历图
广度优先搜索以“广度”作为关键词,每次以扩散的方式向外访问顶点。和树的遍历一样,使用BFS遍历图需要使用一个队列,通过反复取出队首顶点,将该顶点可到达的未曾加入过队列的顶点全部入队,(而不是未被访问)直到队列为空时遍历结束。
可以查看下面的伪代码,根据思路可以使用邻接表和临界矩阵进行实现。
BFS(u){ //遍历u所在的连通块
queue q;//定义队列q
将u入队;
inq[u] = true;
while(q 非空){
取出队首元素u进行访问;
for(从u出发可达到的所有顶点v)
if( inq[v] == false) { //如果v未曾加入过队列
将v入队;
inq[v] = true;
}
}
}
BFSTrave(G){
for(G 的所有顶点u)
if(inq[u] == false){ //如果u未曾加入过队列
BFS(u); //遍历u所在的连通块
}
}
最短路径问题:
最小生成树(Minimum Spanning Tree,MST):
是在一个给定的无向图G(V,E)中求一棵树T,使这棵树拥有图G中的所有顶点,且所有边都来自图G中,并且满足整棵树的边权之和最小。
最小生成树有三个性质需要掌握:
1. 最小生成树是树,因此其边数等于顶点数减一,且树内一定不会有环。
2. 对给定的图G(V,E),其最小生成树可以不唯一,但其边权之和一定是唯一的。
3. 由于最小生成树是在无向图上生成的,因此其根节点可以是这棵树上的任意一个结点。一般为了输出唯一,会指定一个结点作为根节点。
常用的算法有:Prim(普利姆算法)和 Kruskal算法(克鲁斯卡尔算法)
Prim算法
伪代码如下,其时间复杂度为O(V^2),如果图用邻接表实现,可以使用堆优化即使用优先级队列将复杂度降低为O(VlogV + E):
G为图,S是以及加入图中的顶点集,数组d为顶点与集合S的最短距离
Prim(G,d[]){
初始化G[],d[],d[1] = 0;
for(循环n次)
{
u = 使d[u]最小的还未被访问的顶点的标号;
记录u已被访问;
for(从 u 出发能到达的所有顶点v){
if(v 未被访问 && 以u为中介点使得v与集合S的最短距离d[v]更优){
将G[u][v]赋值给d[v];
}
}
}
}
Kruskal算法
伪代码如下,其时间负责度主要在拍于函数上,是O(ElogE),其中E是图的边数。
int kruskal(){
令最小生成树的边权之和为ans,最小生成树的当前边数Num_edge;
将所有边按照边权从小到达排序;
for(从小到大枚举所有边)
{
if(当前测试边的两个端点在不同的连通块中){ //判断是否在一个联通块中可以使用并查集
将该测试边加入最小生成树;
ans += 测试边的边权;
最小生成树的当前边数num_edge+1;
当前边数num_edge 等于定点数减一时结束循环;
}
}
return ans;
}
从上面的时间复杂度分析可知,Prim 算法的时间复杂度与V相关,适合稠密图(顶点少边多),而kruskal算法的实际复杂度与E的数目有关,适合稀疏图(顶点多,边少)。
拓扑排序:
关键路径:
参考内容:《算法笔记》 胡凡 曾磊主编
以上是关于图结构和相关问题的主要内容,如果未能解决你的问题,请参考以下文章