图可以用G=(V,E)来表示,每个图都包括一个顶点集合V和一个边集合E,顶点总数记为|V|,边总数记为|E|
- 稀疏图:边数较少的图
- 密集图:边数较多的图
- 完全图:包含所有可能边的图
- 带权图:边上标有权的图
- 邻接点:一条边所连的两个顶点
- 简单路径:路径上不包含重复顶点的图
- 回路:将某个顶点连接到本身,且长度大于等于3的路径
- 无环图:不带回路的图
图的表示
图有两种常用的表示方法:
- 邻接矩阵
- 邻接表
1.邻接矩阵
使用一个二维矩阵来表示图:
- (i,j)=1,表示顶点i到顶点j之间有一条边(非带权图)
- (i,j)=n,表示顶点i到顶点j之间有一条权重为n的边(带权图)
使用邻接矩阵的空间代价总是O(|V|^2)
2.邻接表
邻接表使用一个顶点指针数组来表示:
- 数组的元素i表示顶点i的指针,它是一个链表的头结点
- 链表其余的顶点表示与顶点i之间存在边的顶点
邻接表的空间代价与图中边的数目和顶点的数目均有关系。每个顶点要占据一个数组元素的位置,且每条边必须出现在其中某个顶点的边链表中
图的遍历
DFS(深度优先遍历)
DFS会递归地访问它的所有未被访问的相邻顶点:
- 先访问顶点v,把所有与v相关联的边存入栈中;
- 弹出栈顶元素,栈顶元素代表的边所关联的另一个顶点就是要访问的下一个元素k;
- 对k重复对v的操作;
- 重复,直至栈中所有元素都被处理完毕
DFS的执行过程将产生一棵DFS(深度优先搜索)树:
整个DFS的过程如下:
相关题目:
BFS(广度优先遍历)
使用一个队列。对于每个顶点,在访问其它顶点前,检查当前节点所有邻接点。和树的广度优先遍历类似
BFS执行过程将产生一棵BFS(广度优先搜索)树:
整个BFS的过程如下:
拓扑排序
(DAG)有向无环图可以描述这样一种场景:有一组任务,任务的执行顺序之间具有依赖性,一些任务必须在另一些任务完成之后才开始执行,如下图:
在这种场景下,任务之间的依赖关系不能出现环,否则任何一个都无法开始执行。将一个(DAG)有向无环图中所有顶点在不违反先决条件规定的基础上排成线性序列的过程就是拓扑排序
有2种方法实现拓扑排序:
- 基于DFS的方法(递归):当访问某个顶点时,不对这个顶点进行任何处理。当递归返回到这个顶点时,打印这个顶点。这将产生一个逆序的拓扑排序。对其进行一次反序操作就可以得到一个拓扑排序的序列。序列从哪个顶点开始并不重要,只要所有顶点最终都能被访问到
- 基于BFS的方法(迭代):首先访问所有的边,计算指向每个顶点的边数(即计算每个顶点的先决条件数目)。将所有没有先决条件的顶点放入队列,然后开始处理队列。当从顶点中删除一个顶点时,把它打印出来,同时将其所有相邻顶点的先决条件计数减1。当某个相邻顶点的计数为 0时,就将其放入队列。如果还有顶点未被打印,而队列已经为空,则图中必然包含回路
最小生成树
最小生成树(MST)是一个包括图G所有顶点及其部分边的图,包括的边是G的子集,满足下列条件:
- 这个子集中所有边的权之和为所有子集中最小的
- 子集中的边能保证图是连通的
下面是一个最小生成树的例子:
如果上图中使用边(D,F)代替(C,F),可得到另一个最小生成树,即最小生成树可能有多个
最小生成树适合解决如下问题:怎样使连接电路板上一系列接头所需焊接的线路最短,或者怎样使得在几个城市之间建立电话网所需的线路最短
Prim算法
原理:从图中任意一个节点N开始,初始化,MST为N。选出与N相关联的边中权最小的一条边,设其连接顶点N与另一个顶点M。把顶点M和边(N,M)加入MST中。接下来,选出与顶点N或顶点M相关联的边中权最小的一条边,设其连接另一个新顶点,将这条边和新顶点添加到MST中。反复进行这样的处理,每一步都选出一条边来扩展MST,这条边是连接当前已在MST中的某个顶点与一个不在MST中的顶点的所有边中代价最小的
证明Prim算法能生成MST:反证法。设图G=(V,E)不能通过Prim算法生成最小生成树。根据Prim算法中各顶点加入MST的顺序,依次定义图G中各顶点为v0,v1,...,v(n-1)。令ei代表边(vx,vi),其中x<i且i>=1,令ej为Prim算法添加的序号最小的那条(第一条)出现以下情况的边:加入ej后的边集不能被扩展而构成图G的一个MST。换句话说,ej是Prim算法发生错误的第一条边。设T为“真正的”MST。令vp为边ej所关联的顶点,即ej=(vp,vj)
因为T是一个树结构,所以T中将存在一条连接vp和vj的路径,且此路径中一定存在某条边e'连接vu和vw,其中u<j,w>=j。因为ej不是T的一部分,所以把ej加入到T会构成一个回路。又因为Prim算法不能生成一个MST,所以e'的权比ej的权更小。这种情况如下图。但是,Prim算法应选择可能的最小权边,它一定会选择e',而不是ej。这与Prim算法选错了边ej的假设相矛盾。因此,Prim算法一定是正确的
相关题目:
来源:oschina
链接:https://my.oschina.net/u/4344048/blog/4690399