-- 有向图
1、有向图是一副具有方向性的图,是由一组顶点和一组有方向的边组成的,每条方向的边都连着一对有序的顶点。
有向图相关术语:
1、出度: 由某个顶点指出的边的个数称为该顶点的出度。
2、入度: 指向某个顶点的边的个数称为该顶点的入度。
3、有向路径: 由一系列顶点组成,对于其中的每个顶点都存在一条有向边,从它指向序列中的下一个顶点。
4、有向环: 一条至少含有一条边,且起点和终点相同的有向路径。
-- API:
-- 代码:
/**
* 有向图
*/
public class Digraph {
// 顶点数目
private final int V;
// 边数目
private int E;
// 邻接表
private Queue<Integer>[] adj;
// 构造方法
public Digraph(int v) {
// 初始化顶点数目
V = v;
// 初始化边数目
E = 0;
// 初始化邻接表
adj = new Queue[v];
for (int i = 0; i < adj.length; i++) {
adj[i] = new Queue<Integer>();
}
}
// 获取顶点数量
public int V() {return V;}
// 获取边数量
public int E() {return E;}
// 往图中添加一条边v-w
public void addEdge(int v,int w) {
// 将w添加到v邻接表
adj[v].enqueue(w);
// 边数量+1
E++;
}
// 获取某个顶点的邻接表
public Queue<Integer> adj(int v) {return adj[v];}
// 获取该图的反向图
public Digraph reverse() {
// 创建一个图保存反向图
Digraph digraph = new Digraph(V);
// 遍历邻接表,构建反向图
for (int v = 0; v < V; v++) {
for (Integer w : adj(v)) {
digraph.addEdge(w,v);
}
}
return digraph;
}
}
-- 拓扑排序
**拓扑排序的目的是:将所有的顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素**
在现实生活中,我们经常会同一时间接到很多任务去完成,但是这些任务的完成是有先后次序的。以我们学习java
学科为例,我们需要学习很多知识,但是这些知识在学习的过程中是需要按照先后次序来完成的。从java基础,到
jsp/servlet,到ssm,到springboot等是个循序渐进且有依赖的过程。在学习jsp前要首先掌握java基础和html基
础,学习ssm框架前要掌握jsp/servlet之类才行。
为了简化问题,我们使用整数为顶点编号的标准模型来表示这个案例:
此时如果某个同学要学习这些课程,就需要指定出一个学习的方案,我们只需要对图中的顶点进行排序,让它转换
为一个线性序列,就可以解决问题,这时就需要用到一种叫拓扑排序的算法
-- 检测有向图中的环
拓扑排序的目的是:给定一副有向图,将所有的顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素
有向图中有环则不能进行拓扑排序,所有进行拓扑排序前要检查图中是否有环
-- api:
-- 代码:
/**
* 检查有向图是否有环
*/
public class DirectedCycle {
// 记录顶点是否已被搜索
private boolean[] marked;
// 记录是否有顶点
private boolean hasCycle;
// 记录顶点是否在正在搜索队列
private boolean[] onStack;
// 构造方法
public DirectedCycle(Digraph digraph) {
marked = new boolean[digraph.V()];
hasCycle = false;
onStack = new boolean[digraph.V()];
// 遍历所有顶点作为入口进行深度搜索
for (int v = 0; v < digraph.V(); v++) {
// 当前顶点未被搜索,则进行深度搜索
if(!marked[v]) {
dfs(digraph,v);
}
}
}
// 深度优先搜索,检查是否有环
private void dfs(Digraph digraph,int v) {
// 当前顶点标记为已搜索
marked[v] = true;
// 当顶点加入正在搜索队列
onStack[v] = true;
// 遍历当前顶点邻接表
for (Integer w : digraph.adj(v)) {
// 如果邻接表中的w顶点,未被搜索则递归进行搜索
if(!marked[w]) {
dfs(digraph,w);
}
// 检查当前顶点是否在搜索队列中,除非有顶点被搜索第二次,不会不会执行到这里
if(onStack[w]) {
hasCycle = true; // 标记为有环
return;
}
}
// 递归返回时清空搜索队列
onStack[v] = false;
}
// 当前顶点是否有环
public boolean hasCycle() {
return hasCycle;
}
}
-- 顶点排序
如果要把图中的顶点生成线性序列其实是一件非常简单的事,之前我们学习并使用了多次深度优先搜索,我们会发
现其实深度优先搜索有一个特点,那就是在一个连通子图上,每个顶点只会被搜索一次,如果我们能在深度优先搜
索的基础上,添加一行代码,只需要将搜索的顶点放入到线性序列的数据结构中,我们就能完成这件事。
在API的设计中,我们添加了一个栈reversePost用来存储顶点,当我们深度搜索图时,每搜索完毕一个顶点,把该
顶点放入到reversePost中,这样就可以实现顶点排序。
-- 代码:
/**
* 基于深度优先搜索的顶点排序
*/
public class DepthFirstOrder {
// 记录当前顶点是否被搜索
private boolean[] marked;
// 使用栈,保存顶点序列
private Stack<Integer> reversePost;
// 构造方法
public DepthFirstOrder(Digraph digraph) {
// 初始化marked
marked = new boolean[digraph.V()];
// 初始化栈
reversePost = new Stack<Integer>();
// 遍历所有顶点
for (int v = 0; v < digraph.V(); v++) {
// 如果当前顶点未被搜索,则进行深度搜索
if(!marked[v]) {
dfs(digraph,v);
}
}
}
// 使用深度优先搜索,进行顶点排序
private void dfs(Digraph digraph,int v) {
// 标记当前顶点,未已搜索
marked[v] = true;
// 遍历当前顶点邻接表
for (Integer w : digraph.adj(v)) {
// 如果当前顶点未被搜索,则递归继续搜索
if(!marked[w]) {
dfs(digraph,w);
}
}
// 递归返回时,将顶点加入栈中
reversePost.push(v);
}
// 获取顶点线性序列
public Stack<Integer> reversePost() {
return reversePost;
}
}
-- 拓扑排序
前面已经实现了环的检测以及顶点排序,那么拓扑排序就很简单了,基于一幅图,先检测有没有环,如果没有环,
则调用顶点排序即可。
-- api:
-- 代码:
/**
* 拓扑排序
*/
public class TopoLogical {
// 顶点的拓扑排序
private Stack<Integer> order;
// 构造方法
public TopoLogical(Digraph digraph) {
// 创建检查环对象
DirectedCycle directedCycle = new DirectedCycle(digraph);
// 如果不存在环,则进行顶点排序
if(!directedCycle.hasCycle()) {
// 创建顶点排序对象
DepthFirstOrder depthFirstOrder = new DepthFirstOrder(digraph);
// 返回顶点排序序列
order = depthFirstOrder.reversePost();
}
}
// 判断顶点是否有环
public boolean idCycle() {
return order == null;
}
// 获取拓扑排序的所有顶点
public Stack<Integer> order() {
return order;
}
}
-- 测试代码:
/**
* 测试拓扑排序
*/
public class OrderTest {
@Test
public void orderTest() {
// 创建一个无向图
Digraph digraph = new Digraph(6);
// 添加有向边
digraph.addEdge(0,2);
digraph.addEdge(0,3);
digraph.addEdge(2,4);
digraph.addEdge(3,4);
digraph.addEdge(4,5);
digraph.addEdge(1,3);
// 创建拓扑排序对象
TopoLogical topoLogical = new TopoLogical(digraph);
Stack<Integer> order = topoLogical.order();
for (Integer w : order) {
System.out.println(w);
}
}
}
-- 运行结果:
@ 以上内容属于个人笔记