持续学习&持续更新中…
学习态度:脚踏实地
代码实现图时并没有采用传统的邻接矩阵或者邻接表,因为它们都太复杂、太麻烦了。
代码实现采用了一种比较折中的方案,比较偏向于邻接表。
代码实现图时,一般实现为有向图,因为无向图可以用有向图来表达。
图的实现方案
邻接矩阵
邻接表
邻接表只需要一个一维数组
图的基础接口
public interface Graph<V, E> {
int vertexSize(); // 顶点的数量
int edgeSize(); // 边的数量
void addVertex(V v); // 添加一个顶点
void removeVertex(V v); // 删除一个顶点
void addEdge(V from, V to); // 添加一条边
void addEdge(V from, V to, E weight); // 添加一条边(带权值)
void removeEdge(V from, V to); // 删除一条边
}
顶点、边的定义
public class ListGraph<V, E> implements Graph<V, E> {
// 顶点
private static class Vertex<V, E> {
V value; // 顶点存储的元素
Set<Edge<V, E>> inEdges = new HashSet<>(); // 以该顶点为终点的边(到达该顶点的边)
Set<Edge<V, E>> outEdges = new HashSet<>(); // 以该顶点为起点的边(从该顶点出发的边)
Vertex(V value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
Vertex<V, E> vertex = (Vertex<V, E>) o;
return Objects.equals(value, vertex.value);
}
@Override
public int hashCode() {
return value != null ? value.hashCode() : 0;
}
@Override
public String toString() {
return value == null ? "null" : value.toString();
}
}
// 边
private static class Edge<V, E> {
E weight; // 边的权值
Vertex<V, E> from; // 这条边从哪个顶点出发
Vertex<V, E> to; // 这条边要到达哪个顶点
Edge(Vertex<V, E> from, Vertex<V, E> to, E weight) {
this.from = from;
this.to = to;
this.weight = weight;
}
@Override
public boolean equals(Object o) {
Edge<V, E> edge = (Edge<V, E>) o;
return from.equals(edge.from) && to.equals(edge.to);
}
@Override
public int hashCode() {
int result = 0;
result = 31 * result + from.hashCode();
result = 31 * result + to.hashCode();
return result;
}
@Override
public String toString() {
return "Edge{" +
"from=" + from +
", to=" + to +
", weight=" + weight +
'}';
}
}
}
图的基础实现
public class ListGraph<V, E> implements Graph<V, E> {
private final Map<V, Vertex<V, E>> vertices = new HashMap<>();
private final Set<Edge<V, E>> edges = new HashSet<>();
public void print() {
System.out.println("顶点:");
vertices.forEach((k, v) -> {
System.out.println(k);
System.out.println("in:");
System.out.println(v.inEdges);
System.out.println("out:");
System.out.println(v.outEdges);
System.out.println("---------------------");
});
System.out.println("边:");
edges.forEach(System.out::println);
}
@Override
public int vertexSize() {
return vertices.size();
}
@Override
public int edgeSize() {
return edges.size();
}
@Override
public void addVertex(V v) {
if (vertices.containsKey(v)) return;
vertices.put(v, new Vertex<>(v));
}
@Override
public void removeVertex(V v) {
// Vertex<V, E> vertex = vertices.get(v);
// if (null == vertex) return;
final Vertex<V, E> removeVertex = vertices.remove(v);
if (null == removeVertex) return;
for (Iterator<Edge<V, E>> iterator = removeVertex.outEdges.iterator(); iterator.hasNext(); ) {
final Edge<V, E> edge = iterator.next();
edges.remove(edge);
edge.to.inEdges.remove(edge);
// iterator.remove();
}
// removeVertex.outEdges.clear();
removeVertex.outEdges = null;
for (Iterator<Edge<V, E>> iterator = removeVertex.inEdges.iterator(); iterator.hasNext(); ) {
final Edge<V, E> edge = iterator.next();
edges.remove(edge);
edge.from.outEdges.remove(edge);
// iterator.remove();
}
// removeVertex.inEdges.clear();
removeVertex.inEdges = null;
}
@Override
public void addEdge(V from, V to) {
addEdge(from, to, null);
}
// 如果发现某个顶点不存在那么需要创建该顶点
@Override
public void addEdge(V from, V to, E weight) {
Vertex<V, E> fromVertex = vertices.get(from);
Vertex<V, E> toVertex = vertices.get(to);
if (fromVertex == null) {
fromVertex = new Vertex<>(from);
vertices.put(from, fromVertex);
}
if (toVertex == null) {
toVertex = new Vertex<>(to);
vertices.put(to, toVertex);
}
Edge<V, E> edge = new Edge<>(fromVertex, toVertex, weight);
if (fromVertex.outEdges.remove(edge)) { // 如果已经存在这条边了
toVertex.inEdges.remove(edge);
edges.remove(edge);
}
fromVertex.outEdges.add(edge);
toVertex.inEdges.add(edge);
edges.add(edge);
}
@Override
public void removeEdge(V from, V to) {
Vertex<V, E> fromVertex = vertices.get(from);
Vertex<V, E> toVertex = vertices.get(to);
if (fromVertex == null || toVertex == null) {
return;
}
Edge<V, E> edge = new Edge<>(fromVertex, toVertex, null);
if (edges.remove(edge)) { // 如果存在这条边的话,才需要删除
toVertex.inEdges.remove(edge);
fromVertex.outEdges.remove(edge);
}
}
// 顶点
private static class Vertex<V, E> {
V value; // 顶点存储的元素
Set<Edge<V, E>> inEdges = new HashSet<>(); // 以该顶点为终点的边(到达该顶点的边)
Set<Edge<V, E>> outEdges = new HashSet<>(); // 以该顶点为起点的边(从该顶点出发的边)
Vertex(V value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
Vertex<V, E> vertex = (Vertex<V, E>) o;
return Objects.equals(value, vertex.value);
}
@Override
public int hashCode() {
return value != null ? value.hashCode() : 0;
}
@Override
public String toString() {
return value == null ? "null" : value.toString();
}
}
// 边
private static class Edge<V, E> {
E weight; // 边的权值
Vertex<V, E> from; // 这条边从哪个顶点出发
Vertex<V, E> to; // 这条边要到达哪个顶点
Edge(Vertex<V, E> from, Vertex<V, E> to, E weight) {
this.from = from;
this.to = to;
this.weight = weight;
}
@Override
public boolean equals(Object o) {
Edge<V, E> edge = (Edge<V, E>) o;
return from.equals(edge.from) && to.equals(edge.to);
}
@Override
public int hashCode() {
int result = 0;
result = 31 * result + from.hashCode();
result = 31 * result + to.hashCode();
return result;
}
@Override
public String toString() {
return "Edge{" +
"from=" + from +
", to=" + to +
", weight=" + weight +
'}';
}
}
}
图的遍历
广度优先搜索
使用广度优先搜索遍历图时,从不同的顶点出发,遍历到的结果是不一样的(需要注意的是,有的顶点有时是遍历不到的)
// 广度优先搜索
@Override
public void bfs(V begin) {
final Vertex<V, E> beginVertex = vertices.get(begin);
if (null == beginVertex) return;
Queue<Vertex<V, E>> queue = new LinkedList<>();
Set<Vertex<V, E>> set = new HashSet<>();
queue.offer(beginVertex);
set.add(beginVertex);
while (!queue.isEmpty()) {
final Vertex<V, E> vertex = queue.poll();
System.out.println(vertex); // 这里的遍历只是简单的打印一下顶点
for (Edge<V, E> edge : vertex.outEdges) {
if (set.contains(edge.to)) continue;
queue.offer(edge.to);
set.add(edge.to);
}
}
}
深度优先搜索
使用深度优先搜索遍历图时,从不同的顶点出发,与广度优先搜索一样,遍历到的结果也会是不一样的(需要注意的是,有的顶点有时是遍历不到的);
使用深度优先搜索遍历图时,从相同的顶点出发进行遍历,也会有很多条路径可以走。
// 深度优先搜索——递归实现
@Override
public void dfs(V begin) {
final Vertex<V, E> beginVertex = vertices.get(begin);
if (null == beginVertex) return;
dfs(beginVertex, new HashSet<>());
}
private void dfs(Vertex<V, E> vertex, Set<Vertex<V, E>> set) {
System.out.println(vertex);
set.add(vertex);
for (Edge<V, E> edge : vertex.outEdges) {
if (set.contains(edge.to)) continue;
dfs(edge.to, set);
}
}
// 深度优先搜索——非递归实现
@Override
public void dfs(V begin) {
final Vertex<V, E> beginVertex = vertices.get(begin);
if (null == beginVertex) return;
Set<Vertex<V, E>> set = new HashSet<>();
Stack<Vertex<V, E>> stack = new Stack<>();
stack.push(beginVertex);
set.add(beginVertex);
System.out.println(beginVertex);
while (!stack.isEmpty()) {
final Vertex<V, E> vertex = stack.pop();
for (Edge<V, E> edge : vertex.outEdges) {
if (set.contains(edge.to)) continue;
stack.push(edge.from);
stack.push(edge.to);
set.add(edge.to);
System.out.println(edge.to);
break;
}
}
}
修改遍历接口
public interface Graph<V, E> {
int vertexSize(); // 顶点的数量
int edgeSize(); // 边的数量
void addVertex(V v); // 添加一个顶点
void removeVertex(V v); // 删除一个顶点
void addEdge(V from, V to); // 添加一条边 // 如果发现某个顶点不存在那么自动创建该顶点
void addEdge(V from, V to, E weight); // 添加一条边(带权值) // 如果发现某个顶点不存在那么自动创建该顶点
void removeEdge(V from, V to); // 删除一条边
// void bfs(V begin); // 广度优先搜索遍历
// void dfs(V begin); // 深度优先搜索遍历
void bfs(V begin, Visitor<V> visitor); // 广度优先搜索遍历
void dfs(V begin, Visitor<V> visitor); // 深度优先搜索遍历
interface Visitor<V> {
void vertex(V v);
}
}
// 广度优先搜索
@Override
public void bfs(V begin, Visitor<V> visitor) {
if (null == visitor) return;
final Vertex<V, E> beginVertex = vertices.get(begin);
if (null == beginVertex) return;
Queue<Vertex<V, E>> queue = new LinkedList<>();
Set<Vertex<V, E>> set = new HashSet<>();
queue.offer(beginVertex);
set.add(beginVertex);
while (!queue.isEmpty()) {
final Vertex<V, E> vertex = queue.poll();
visitor.vertex(vertex.value);
for (Edge<V, E> edge : vertex.outEdges) {
if (set.contains(edge.to)) continue;
queue.offer(edge.to);
set.add(edge.to);
}
}
}
// 深度优先搜索——非递归实现
@Override
public void dfs(V begin, Visitor<V> visitor) {
if (null == visitor) return;
final Vertex<V, E> beginVertex = vertices.get(begin);
if (null == beginVertex) return;
Set<Vertex<V, E>> set = new HashSet<>();
Stack<Vertex<V, E>> stack = new Stack<>();
stack.push(beginVertex);
set.add(beginVertex);
visitor.vertex(beginVertex.value);
while (!stack.isEmpty()) {
final Vertex<V, E> vertex = stack.pop();
for (Edge<V, E> edge : vertex.outEdges) {
if (set.contains(edge.to)) continue;
stack.push(edge.from);
stack.push(edge.to);
set.add(edge.to);
visitor.vertex(edge.to.value);
break;
}
}
// 深度优先搜索——递归实现
public void dfs2(V begin, Visitor<V> visitor) {
if (null == visitor) return;
final Vertex<V, E> beginVertex = vertices.get(begin);
if (null == beginVertex) return;
dfs2(beginVertex, new HashSet<>(), visitor);
}
private void dfs2(Vertex<V, E> vertex, Set<Vertex<V, E>> set, Visitor<V> visitor) {
visitor.vertex(vertex.value);
set.add(vertex);
for (Edge<V, E> edge : vertex.outEdges) {
if (set.contains(edge.to)) continue;
dfs2(edge.to, set, visitor);
}
}
}
如果想要在遍历的过程中可以停止(退出)遍历的话:
void bfs(V begin, Visitor<V> visitor); // 广度优先搜索遍历
void dfs(V begin, Visitor<V> visitor); // 深度优先搜索遍历
// 可以停止遍历
interface Visitor<V> {
boolean vertex(V v); // 返回true,就终止遍历
}
// 可以停止遍历
// 广度优先搜索
@Override
public void bfs(V begin, Visitor<V> visitor) {
if (null == visitor) return;
final Vertex<V, E> beginVertex = vertices.get(begin);
if (null == beginVertex) return;
Queue<Vertex<V, E>> queue = new LinkedList<>();
Set<Vertex<V, E>> set = new HashSet<>();
queue.offer(beginVertex);
set.add(beginVertex);
while (!queue.isEmpty()) {
final Vertex<V, E> vertex = queue.poll();
if (visitor.vertex(vertex.value)) return;
for (Edge<V, E> edge : vertex.outEdges) {
if (set.contains(edge.to)) continue;
queue.offer(edge.to);
set.add(edge.to);
}
}
}
// 深度优先搜索——非递归实现
@Override
public void dfs(V begin, Visitor<V> visitor) {
if (null == visitor) return;
final Vertex<V, E> beginVertex = vertices.get(begin);
if (null == beginVertex) return;
Set<Vertex<V, E>> set = new HashSet<>();
Stack<Vertex<V, E>> stack = new Stack<>();
stack.push(beginVertex);
set.add(beginVertex);
if (visitor.vertex(beginVertex.value)) return;
while (!stack.isEmpty()) {
final Vertex<V, E> vertex = stack.pop();
for (Edge<V, E> edge : vertex.outEdges) {
if (set.contains(edge.to)) continue;
stack.push(edge.from);
stack.push(edge.to);
set.add(edge.to);
if (visitor.vertex(edge.to.value)) return;
break;
}
}
}
AOV网
作业:自学《AOV网络》
拓扑排序
卡恩算法的思路是对的,但代码实现时,我们肯定不能将顶点删掉,因为这样会破坏原有的图结构,所以我们代码使用卡恩算法实现拓扑排序时,应该变通一下:
// 使用卡恩算法对DAG进行拓扑排序
@Override
public List<V> topologicalSort() {
List<V> list = new ArrayList<>(); // list用来存放遍历结果,返回给使用者
Queue<Vertex<V, E>> queue = new LinkedList<>(); // queue用来存放入度为0的顶点
Map<Vertex<V, E>, Integer> map = new HashMap<>(); // 顶点的入度表
// 初始化入度表和queue(将度为0的节点都放入队列)
vertices.forEach((v, vertex) -> {
int inSize = vertex.inEdges.size();
if (inSize == 0) queue.offer(vertex);
else
// 初始化入度表时,没有必要将入度为0的顶点放入入度表中
map.put(vertex, inSize);
});
while (!queue.isEmpty()) {
Vertex<V, E> vertex = queue.poll();
list.add(vertex.value);
vertex.outEdges.forEach(edge -> {
Integer integer = map.get(edge.to);
integer--;
if (integer == 0)
// 将这个顶点入队后,就不用更新该顶点的入度了
queue.offer(edge.to);
else map.put(edge.to, integer);
});
}
return list;
}
测试:
public class Data {
public static final Object[][] TOPO = {
{0, 2},
{1, 0},
{2, 5}, {2, 6},
{3, 1}, {3, 5}, {3, 7},
{5, 7},
{6, 4},
{7, 6}
};
}
/**
* 有向图
*/
private static Graph<Object, Double> directedGraph(Object[][] data) {
Graph<Object, Double> graph = new ListGraph<>();
for (Object[] edge : data) {
if (edge.length == 1) {
graph.addVertex(edge[0]);
} else if (edge.length == 2) {
graph.addEdge(edge[0], edge[1]);
} else if (edge.length == 3) {
double weight = Double.parseDouble(edge[2].toString());
graph.addEdge(edge[0], edge[1], weight);
}
}
return graph;
}
/**
* 无向图
*/
private static Graph<Object, Double> undirectedGraph(Object[][] data) {
Graph<Object, Double> graph = new ListGraph<>();
for (Object[] edge : data) {
if (edge.length == 1) {
graph.addVertex(edge[0]);
} else if (edge.length == 2) {
graph.addEdge(edge[0], edge[1]);
graph.addEdge(edge[1], edge[0]);
} else if (edge.length == 3) {
double weight = Double.parseDouble(edge[2].toString());
graph.addEdge(edge[0], edge[1], weight);
graph.addEdge(edge[1], edge[0], weight);
}
}
return graph;
}
private static void testTopologicalSort() {
Graph<Object, Double> graph = directedGraph(Data.TOPO);
graph.topologicalSort().forEach(System.out::println);
}
public static void main(String[] args) {
testTopologicalSort();
}
参考
小码哥李明杰老师课程: 恋上数据结构与算法 第二季.
本文完,感谢您的关注支持!