图
G=(V,E) V顶点 E边
图的概念及术语
1.无向图
2.有向图
3.简单图:无重复边 没有结点到自身的边
4.多重图:有重复边 有结点到自身的边
5.完全图
无向完全图:任意两个顶点都存在边 n个顶点的无向完全图有$\frac{n(n-1)}{2}$个边
有向完全图:任意两个顶点间都存在方向相反的边 n个顶点的有向完全图有n(n-1)个边
6.子图
图本身就是自己的子图
生成子图:子图中包含了原图的所有顶点
7.连通 强连通
无向图对应连通图 有向图对应强连通图
| 无向图 | 连通图 | 有向图 | 强连通图 |
|---|---|---|---|
| 顶点u-w间有路径存在 则u w连通 | 任意两个结点都连通的图叫连通图 | u>w w>u都有路径存在 则u w强连通 | 任意两个结点都强连通的图叫强连通图 |
| 连通图:n个顶点最少n-1个边 | - | 强连通图:n个顶点最少n个边 | - |
| 连通分量=极大连通子图: | 包含了所有边的连通子图 | 强连通分量=极大强连通子图 | 包含了所有边的强连通子图 |
8.极小连通子图:边数最少的连通子图
9.生成树:连通图中包含了全部结点的一个极小连通子图(不唯一) n个结点n-1条边
生成森林:非连通图所有连通分量的生成树组成生成森林(不唯一)
10.度
n个结点e个边的无向图中度的总数为2e
n个结点e个边的有向图中入度=出度=e
11.网(带权图):对边赋予权值 就成了网
12.稀疏图/稠密图:当|E|<|V|log|E|时 该图为稀疏图 反之为稠密图
图的存储
邻接矩阵
一维数组存结点 二维数组存边 所以空间复杂度$O(n^2)$
1 | #define Maxsize 100 |
性质:
适合稠密图
有向图邻接矩阵中一行非零的个数为该点的入度 一列非零的个数为该点的出度
无向图的邻接矩阵为实对称矩阵 $A=A^T$ 一行/列非零个数为该点的度
邻接矩阵$A$ $A^n元素A^n[i][j]=X 其中X=i-j$路径长度为n的路径的个数
针对稀疏图 邻接矩阵内存放了很多0/无意义的数据 造成了空间上的浪费 因此针对稀疏图我们采用邻接表的方式进行存储
邻接表
为每个结点建立一个与之相连的单链表存放与结点相连的边
顶点表为顺序存储 存顶点的数据 存边表的头指针
边表为链式存储 存放与该结点相连的所有边
1 | #define Maxsize |
性质:
适合稀疏图
因为对结点相连的边选择顺序不同 所以邻接表不唯一
有向图邻接表存储空间O(|V|+|E|) 结点对应边表的长度为该结点的出度 入度需要遍历邻接表计算
无向图邻接表存储空间O(|V|+2|E|) 结点对应边表的长度为该结点的度
邻接矩阵与邻接表比较
| 邻接矩阵 | vs | 邻接表 |
|---|---|---|
| 适合稠密图 | 适用性 | 适用稀疏图 |
| 效率高 | 判断两个顶点间是否存在边 | 效率低 |
| 效率低 | 找出某顶点相邻的边 | 效率高 |
十字链表
十字链表是有向图的一种链式存储结构
邻接多重表
邻接多重表是无向图的一种链式存储结构
图的遍历
广度优先搜索BFS
通过一个队列和一个数组来实现
1 | bool visited[Maxsize];//辅助数组visited来标记对应编号的顶点是否被访问过 |
BFS算法性能分析: 空间复杂度$O(|V|)$ 时间复杂度 G为邻接矩阵 $O(|V|^2)$ G为邻接表 $O(|V|+|E|)$
可以利用BFS解决无权图的单源最短路径问题
可以利用BFS得到广度优先生成树 邻接矩阵的广度优先生成树唯一 邻接表的广度优先生成树不唯一(邻接表的边表序列不唯一)
深度优先搜索DFS
通过递归或者栈实现
1 | bool visited[Maxsize];//标记数组 |
DFS算法性能分析: 空间复杂度$O(|V|)$ 时间复杂度 G为邻接矩阵 $O(|V|^2)$ G为邻接表 $O(|V|+|E|)$
深度优先遍历得出的序列得到深度优先生成树/森林
邻接矩阵的深度优先生成树唯一 邻接表的深度优先生成树不唯一(邻接表的边表序列不唯一)
遍历和连通性
在无向图中 BFS()与DFS()的调用次数为连通分量的个数
图的应用
最小生成树
最小生成树就是极小连通子图 包含了所有顶点和最少的边 权值唯一且最小 |E|=|V|-1
性质:带权无向连通图才有最小生成树 最小生成树不一定唯一 当所有的边权值都不一样时才有唯一的最小生成树
图有n个顶点n-1个边时 最小生成树就是它自己
从策略上来说,Prim算法是直接查找,多次寻找邻边的权重最小值,而Kruskal是需要先对权重排序后查找的
所以说,Kruskal在算法效率上是比Prim快的,因为Kruskal只需一次对权重的排序就能找到最小生成树,而Prim算法需要多次对邻边排序才能找到
Prim算法
适用于稠密图
首先以一个结点作为最小生成树的初始结点,然后以迭代的方式找出最小生成树中各结点权重最小的边,并加到最小生成树中。(加入之后如果产生回路了就要跳过这条边,选择下一个结点)当所有的结点都加入到最小生成树中后,就找出了这个连通图的最小生成树
Kruskal算法
Kruskal算法在找最小生成树结点之前,需要对权重从小到大进行排序。将排序好的权重边依次加入到最小生成树中(如果加入时产生回路就跳过这条边,加入下一条边),当所有的结点都加入到最小生成树中后,就找到了这个连通图的最小生成树