图
1、 定义
G=(V,E),图G由顶点集V和边集E组成。
线性表可以为空表,树可以是空树,但图不能是空图。图可以只有顶点而无边。
- 有向图<v,w>
v是弧头,w是弧尾 - 无向图(v,w)(w,v)
- 简单图
a. 不存在重复边
b. 不存在顶点到自身的边 - 多重图
与简单图相对 - 完全图
任意两点之间都存在边
n个顶点, 无向完全图有n(n-1)/2条边, 有向完全图有n(n-1)条边
- 子图
- 连通、连通图和连通分量
连通是指两点间有路径,而连通图就是图中任意两点间有连通。
连通分量是指无向图的极大连通子图。
- 结论:如果一个图有n个顶点,并且有小于n-1条边,则此图必是非连通图。
极大连通子图:连通子图包含图的所有边
极小连通子图:既要保持图连通又要使得边数最少的子图
- 强连通图、强连通分量
强连通指从顶点a到顶点b之间有路径
- 生成树、生成森林
生成树是包含图中全部顶点的一个极小连通图
最小生成树,树形可能不唯一,但代价一定唯一 - 顶点的度、入度、出度
度为某顶点所在的边数
无向图的全部顶点的度的和等于边数的两倍
有向图的全部顶点的入度之和与出度之和相等,并等于边数 - 边的权和网
- 稠密图、稀疏图
- 路径、路径长度和回路
- 简单路径、简单回路
顶点不重复出现的路径称为简单路径。
对于回路,除了第一个和最后一个顶点其余顶点不重复出现的回路称为简单回路
错误说法:1. 回路是简单路径 2. 最短路径允许有环
正确说法:最短路径一定是简单路径
- 距离
- 有向树
是指一个顶点入度为0,其余顶点入度为1的有向图。
2、 图结构的存储
2.1 邻接矩阵
A[i][j] = 1, 若(vi,vj)是E(G)的边
A[i][j] = 0, 若(vi,vj)不是E(G)的边
2.2 邻接表
稀疏图利用邻接矩阵存储,会浪费大量的空间。
注:
- G为无向图,存储空间需要O(|V|+2|E|)
G为有向图,存储空间需要O(|V|+|E|) - 有向图的邻接表表示,求一个给定顶点的出度只需要计算其邻接表中的结点个数;但求其顶点的入度需要遍历全部的邻接表
- 图的邻接表表示可能不唯一
2.3 邻接多重表
用于无向图存储
2.4 十字链表
用于有向图存储
3、 图的遍历
3.1 广度优先遍历(BFS)
bool visited[MAX_VERTEX_NUM]; // 设置辅助数组visit[]标记顶点是否被访问过
void BFSTraverse(Graph G){
for(i=0; i<G.vexnum; ++i)
visited[i] = FALSE;
InitQueue(Q);
for(i=0; i<G.vexnum; ++i)
if(visited[i]) // 对每个连通分量进行一次BFS
BFS(G, i); // Vi点未坊问,进行BFS
}
void BFS(Graph G,int v){
visit(v);
visited[v] = TRUE; // 对V进行已访问标记
EnQueue(Q, v);
while(!isEmpty(Q)){
DeQueue(Q, v);
for(w=FirstNeighbor(G,v); w>=0; w=NextNeighbor(G,v,w))
if(!visited[w]){ // w为v的尚未访问的邻接顶点
visit(w);
visited[w] = TRUE;
EnQueue(Q,w);
}
}
}
空间复杂度O(|V|)
用邻接表存储时间复杂度为O(|V|+|E|),V是访问顶点时间,E是访问边时间
用邻接矩阵时间复杂度为O(|V|2)
3.2 深度优先遍历(DFS)
bool visited[MAX_VERTEX_NUM]; // 访问标记数组
void DFSTraverse(Graph G){
for(v=0; v<G.vexnum; ++v)
visited[i] = FALSE;
InitQueue(Q);
for(v=0; v<G.vexnum; ++v)
if(visited[v])
BFS(G, v);
}
void DFS(Graph G,int v){
visit(v);
visited[v] = TRUE;
for(w=FirstNeighbor(G,v); w>=0; w=NextNeighbor(G,v,w))
if(!visited[w]){
DFS(G,w);
}
}
- DFS为递归算法,需要借助一个栈
- 空间与时间复杂度和BFS相同
- 同样一个图,基于邻接矩阵所遍历的BFS序列与DFS序列相同,但基于邻接表的遍历序列可能不一样
4、 应用
4.1 最小生成树
生成树是图的极小连通子图。减少一条边会变成非连通图,增加一条边会形成一条回路。
最小生成树性质:
a. 最小生成树树形不唯一
b. 最小生成树的总权值固定
c. 最小生成树边数=顶点数-1
- Prim算法
思想:每回合从已有顶点集向外扩散,选择代价最小边加入集合
- Kruskal算法
思想:每回合直接从边集合中以不够成回路原则选择最小代价边加入。适合边稀疏而顶点多的图
4.2 最短路径
-
Dijkstra求单源最短路径
-
Floyd求各顶点间最短路径
注:图片源自青岛大学-王卓老师,B站上有老师的数据结构课程视频,讲解的很详细。
4.3 拓扑排序
有向无环图:一个有向图中没有环,简称DAG图
AOV网:用顶点表示活动的网络
AOE网:用边表示活动的网络
对DAG图拓扑排序:
- 从图中选择一个没有前驱的顶点输出
- 从图中删除该顶点以及以它为起点的有向边
- 重复1,2,直至当前图为空或仅存在环
若用邻接表存储,拓扑排序的时间复杂度为O(n+e)
邻接矩阵则为O(n2)
4.4 关键路径
关键路径:从源点到汇点的所有路径中,具有最大路径长度的路径。
考研tips:
- 算法:深度优先搜索、广度优先搜索
- 选择题:图的概念和性质、图的存储结构(邻接矩阵、邻接表、邻接多重表和十字链表)、存储结构之间的转化、基于存储结构上的遍历操作与应用(拓扑排序、最小生成树、最短路径和关键路径)
小狼的相关博文: