图
无向图
图:是由一组顶点和一组能够将两个顶点相连的边组成的。点: Vertex, 边:Edge;
路径: 是由边顺序连接的一系列顶点。
连通图:从任意一个顶点都存在一条路径到达另一个任意顶点,称为连通图。
树:是一副无环连通图。
当且仅当一幅含有 V 个结点的图 G 满足下列5个条件之一时,它就是一个树:
- G 有 V- 1 条边且不含有环
- G 有 V- 1条边且是连通的
- G 是连通的, 但删除任意一条边都会使它不再连通
- G 是无环图,但添加任意一条边都会产生一个环
- G 中的任意一对顶点之间仅存在一条简单路径。
邻接表
定义:将每个顶点的所有相邻顶点都保存在该顶点对应的元素所指向的一张链表中。
特点:
- 使用的空间和 V + E 成正比
- 添加一条边所需的时间为常数
- 遍历顶点 V 的所有相邻顶点所需的时间和 V 的度数成正比;
图的 API 表示:
|
|
深度优先搜索— DFS
要搜索一副图,只需用一个递归方法来遍历所有的顶点,在访问其中一个顶点时:
- 将它标记为已访问;
- 递归地访问它的所有没有被标记过的邻居顶点。
解决问题:
- 连通性,两个顶点是否连通?
- 单点路径问题,从 s 到给定目的顶点v 是否存在一条路径?
寻找路径
使用深度优先搜索查找图中的路径
DepthFirstPaths.java
其中 edge[]
记录着每个顶点到起点的路径,而不是记录当前顶点到起点的路径;例如:在由边 v - w 第一次访问任意 w 时,将 edge[w] = v
来记录这条路径。换句话说,v - w 是从 s 到 w 的路径上的最后一条已知边;
|
|
要找出 s 到任意顶点 v 的路径,pathTo()
方法用变量 x
遍历整棵树,将 x
设为 edge[x]
,然后在到达 s 之前,将遇到的所有顶点都压入栈中。将这个栈返回为一个 Iterable 对象帮助用例遍历 s 到 v 的路径。
深度优先只是解决了从一个顶点到另一个顶点之间是否存在一条路径的问题;
深度优先搜索是基于栈的;
广度优先搜索 BFS
广度优先搜索是基于队列的;
使用一个队列来保存所有已经被标记过但其邻接表还未被检查过的顶点;
先将起点加入队列,然后从队列中将起点删除,并将相邻顶点加入队列中,然后执行下面步骤:
- 取队列的下一个顶点 v 并标记它;
- 将与 v 相邻的所有未标记过的顶点加入队列;
重复上述步骤直到队列为空;
JAVA 实现:
|
|
连通分量
基于深度优先搜索的解决方案如下,同样可以使用 union_find 来检查连通性;
java 实现:
|
|
检测环
给定的图是不是无环图?
java 实现如下:
|
|
有向图
最小生成树
加权图
定义:一种为每条边关联一个权值或者是成本的模型;
最小生成树
定义:图的生成树是它的一棵含有其所有顶点的无环连通子图。一幅加权图的最小生成树(MST) 是它的一棵权值(树中所有边的权值之和)最小的生成树;
切分定理
切分定义:图的一种切分是将图的所有顶点分为两个非空且不重叠的两个集合。横切边是一条连接两个属于不同集合的顶点的边;
切分定理:在一幅加权图中,给定任意的切分,它的横切边中的权重最小者必然属于图的最小生成树;
贪心算法
定义:下面这种方法会将含有 V 个顶点的任意加权连通图中属于最小生成树的边标记为黑色:初始状态下所有边均为灰色, 找到一种切分,它产生的横切边均不为黑色。将它权重最小的横切边标记为黑色。反复,直到标记了V - 1条黑色边为止;
加权无向图
在邻接表中,在链表的结点中增加一个权重域;
Edge 的实现:
|
|
EdgeWeightedGraph 的实现:
|
|
Prim 算法
命题:Prim 算法能够得到任意加权连通图的最小生成树;
从一棵树的顶点开始,每次总是将下一条连接树的顶点与它的横切边加入树中;
Lazy Prim 延时实现
- 一条优先队列保存所有的横切边, 将失效的边保留在队列中;
- 一个由顶点索引的数组来标记树的顶点;
- 一条队列来保存最小生成树的边;
Java 实现:
|
|
相应的 MinPQ
的实现:
|
|
Prim 即时实现
关注的是每个顶点横切边中的最小值;
将失效的边从队列中删除掉;
将有效的横切边都保存在一条索引优先队列中。
Java 实现:
|
|
相应的 IndexMinPQ
算法:
|
|
Kruskal 算法
思想:按照边的权重顺序(从小到大)来处理它们;
java 实现:
|
|
最短路径
定义:在一幅加权有向图中,从顶点s 到顶点 t 的最短路径是所有从s到t的路径中的权重最小者;
最短路径树
解决的是单点最短路径问题;
定义:给定一幅加权有向图和一个顶点s, 以s 为起点的一棵 最短路径树是图的一幅子图,它包含s和从s可达的所有顶点。这个有向树的根结点是s,树的每条路径都是有向图中的一条最短路径;
加权有向边
Java 实现
|
|
加权有向图
java 实现
|
|
松弛操作 Relaxation
定义: 放松边 v -> w 意味着检查从 s(原点)到 w 的最短路径是否是先从 s 到 v,然后再由 v到w。如果是,则根据这个情况更新数据结构的内容;否则这个边失效;
|
|
Dijkstra 算法
Dijkstra 算法能够解决边权重非负的加权有向图的单起点最短路径问题
需要的数据结构:distTo[] 、edgeTo[]和 IndexMinPQ 索引队列;
索引队列用来保存需要被放松的顶点并确认下一个被放松的顶点。
java 实现:
|
|
应用:
给定两点的最短路径
任意顶点之间的最短路径
java 实现:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869public class DijkstraAllPairsSP {private DijkstraSP[] all;/*** Computes a shortest paths tree from each vertex to to every other vertex in* the edge-weighted digraph {@code G}.* @param G the edge-weighted digraph* @throws IllegalArgumentException if an edge weight is negative* @throws IllegalArgumentException unless {@code 0 <= s < V}*/public DijkstraAllPairsSP(EdgeWeightedDigraph G) {all = new DijkstraSP[G.V()];for (int v = 0; v < G.V(); v++)all[v] = new DijkstraSP(G, v);}/*** Returns a shortest path from vertex {@code s} to vertex {@code t}.* @param s the source vertex* @param t the destination vertex* @return a shortest path from vertex {@code s} to vertex {@code t}* as an iterable of edges, and {@code null} if no such path* @throws IllegalArgumentException unless {@code 0 <= s < V}* @throws IllegalArgumentException unless {@code 0 <= t < V}*/public Iterable<DirectedEdge> path(int s, int t) {validateVertex(s);validateVertex(t);return all[s].pathTo(t);}/*** Is there a path from the vertex {@code s} to vertex {@code t}?* @param s the source vertex* @param t the destination vertex* @return {@code true} if there is a path from vertex {@code s}* to vertex {@code t}, and {@code false} otherwise* @throws IllegalArgumentException unless {@code 0 <= s < V}* @throws IllegalArgumentException unless {@code 0 <= t < V}*/public boolean hasPath(int s, int t) {validateVertex(s);validateVertex(t);return dist(s, t) < Double.POSITIVE_INFINITY;}/*** Returns the length of a shortest path from vertex {@code s} to vertex {@code t}.* @param s the source vertex* @param t the destination vertex* @return the length of a shortest path from vertex {@code s} to vertex {@code t};* {@code Double.POSITIVE_INFINITY} if no such path* @throws IllegalArgumentException unless {@code 0 <= s < V}* @throws IllegalArgumentException unless {@code 0 <= t < V}*/public double dist(int s, int t) {validateVertex(s);validateVertex(t);return all[s].distTo(t);}// throw an IllegalArgumentException unless {@code 0 <= v < V}private void validateVertex(int v) {int V = all.length;if (v < 0 || v >= V)throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));}}
加权有向无环图的最短路径算法
思想:将顶点的放松和拓扑排序结合起来;
首先用拓扑排序得到图的所有顶点,然后放松所有顶点;
java 实现:
|
|
Bellman-Ford 算法
定义:在任意含有 V 个顶点的加权有向图中给定起点 s , 从 s 无法到达任何负权重环,一下算法够解决其中的单点最短路径问题:将 distTo[s] 初始化为 0, 其他 distTo[] 元素初始化为无穷大,以任意顺序放松有向图的所有边,重复 V轮;
需要的数据结构:
- 一条用来保存即将被放松的顶点的队列queue;
- 一个由顶点索引的 boolean 数组 onQ[],用来指示顶点是否已经存在于队列中,防止重复插入;
Java 实现
|
|