之前的博客“【算法导论-35】图算法JGraphT开源库介绍”中提到的开源版本的Graph库。然而,继续《算法导论》的学习必须自己实现Graph。所以,放弃使用该库,实现自己的Graph类。
注意,本篇博客紧密结合《算法导论》第22章,深度优先、广度优先、拓扑排序算法都取自相关章节的伪代码,这里不再讲解相关原理。
基础的Graph类的实现包括以下:
☆支持有向图和无向图两种类型;
☆支持泛型;
☆支持深度优先搜索和广度优先搜索;
☆支持拓扑排序;
在后续章节中,还会涉及到带权重的Graph,到时候再升级。
首先是Color类,这是在深度优先搜索和广度优先搜索时用到的Vertex属性,这里才有enum类型。
public enum Color {
BLACK,WHITE,GRAY
}
Vertex类包含了邻接表(集合表示),以及深度优先、广度优先搜索的初次发现和结束的时间。
import java.util.LinkedHashSet;
import java.util.Set;
/** * * Graph的顶点 参考:《算法导论》22.2节. *
* * created by 曹艳丰 2016-09-05 *
* */
public class Vertex {
public Color color;
public int distance;
public Vertex parent;
public T value;
public int discover,finish;//深度优先搜索的第一次发现和结束的时间
public Set> adjacencyVertices;// 邻接表,采用集合来表示
public Vertex(T t) {
// TODO Auto-generated constructor stub
color = Color.WHITE;
distance = Integer.MAX_VALUE;
parent = null;
value = t;
adjacencyVertices = new LinkedHashSet>();
discover=finish=0;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Vertex> other = (Vertex>) obj;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
@Override
public String toString() {
// TODO Auto-generated method stub
StringBuilder builder=new StringBuilder();
builder.append(value);
return builder.toString();
}
}
有向图和无向图的边不一样,提供一个超类Edge。
/** * * Graph的顶点 * 参考:《算法导论》22.2节. *
* * created by 曹艳丰 * 2016-09-05 *
* */
public abstract class Edge<T> {
public T source ,target;
public Edge(T source ,T target) {
// TODO Auto-generated constructor stub
this.source=source;
this.target=target;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((source == null) ? 0 : source.hashCode());
result = prime * result + ((target == null) ? 0 : target.hashCode());
return result;
}
/** * 有向边和无向边的equal函数不同.有向边要求source→target顺序,无向边不要求 * */
@Override
public abstract boolean equals(Object obj);
}
无向图的边UndirectedEdge。
/** * * 无向Graph的Edge实现 *
* * created by 曹艳丰 * 2016-09-05 *
* */
public class UndirectedEdge<T> extends Edge<T> {
public UndirectedEdge(T source, T target) {
super(source, target);
// TODO Auto-generated constructor stub
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Edge> other = (Edge>) obj;
if (source == null) {
if (other.source != null&&other.target!=null)
return false;
} else if (!source.equals(other.source)&&!source.equals(other.target))
return false;
if (target == null) {
if (other.target != null&&other.source!=null)
return false;
} else if (!target.equals(other.target)&&!target.equals(other.source))
return false;
return true;
}
}
有向图的边DirectedEdge。
/** * * 有向Graph的Edge实现 *
* * created by 曹艳丰 * 2016-09-05 *
* */
public class DirectedEdge<T> extends Edge<T>{
public DirectedEdge(T source, T target) {
super(source, target);
// TODO Auto-generated constructor stub
}
/** *有向边,source和target必须对应相等 * */
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Edge> other = (Edge>) obj;
if (source == null) {
if (other.source != null)
return false;
} else if (!source.equals(other.source))
return false;
if (target == null) {
if (other.target != null)
return false;
} else if (!target.equals(other.target))
return false;
return true;
}
}
然后到了有向图和无向图的超类Graph。Graph提供了邻接表和邻接矩阵两种形式。为了支持深度优先、广度优先搜索,提供了time变量。
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;
/** * * Graph的实现 *
* * 参考:《算法导论》22.1节. *
* * created by 曹艳丰 *
* * 2016-09-05 *
* */
public abstract class Graph {
protected int[][] adjacencyMatrix;// 邻接矩阵
protected int size = 0;// 定点总数
public Set> vertices;// 所有节点
public Set> edges;// 所有边
private int time;
private Stack> topologicalStack;//用于拓扑排序
public Graph() {
// TODO Auto-generated constructor stub
adjacencyMatrix = new int[size][size];
vertices = new LinkedHashSet>();
edges = new LinkedHashSet>();
topologicalStack=new Stack>();
}
public Graph addVertex(T t) {
Vertex vertex = new Vertex(t);
// 如果已经包含该节点,抛出异常
if (vertices.contains(vertex)) {
throw new IllegalArgumentException("该节点已经存在,不能再添加!");
}
size++;
vertices.add(vertex);
adjustMatrix(t);
return this;
}
// 添加边,t1-t2
public abstract void addEdge(T t1, T t2);
// 调整邻接矩阵
private void adjustMatrix(T t) {
int[][] tempyMatrix = new int[size][size];
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - 1; j++) {
tempyMatrix[i][j] = adjacencyMatrix[i][j];
}
}
adjacencyMatrix = tempyMatrix;
}
protected void setMatrixValue(int row, int column) {
adjacencyMatrix[row][column] = 1;
}
// 以邻接矩阵的形式打印Graph
public void printAdjacencyMatrix() {
System.out.println("邻接矩阵形式:");
for (int[] is : adjacencyMatrix) {
for (int i : is) {
System.out.print(i);
System.out.print(" ");
}
System.out.println();
}
}
// 以邻接表的形式打印Graph
public void printAdjacencyVertices() {
System.out.println("邻接表形式:");
for (Vertex vertex : vertices) {
System.out.print(vertex);
for (Vertex vertex2 : vertex.adjacencyVertices) {
System.out.print("→");
System.out.print(vertex2);
}
System.out.println();
}
}
protected Vertex isContainVertext(T t) {
for (Vertex v : vertices) {
if (v.value.equals(t)) {
return v;
}
}
return null;
}
protected Edge isContainEdge(T t1, T t2) {
for (Edge edge : edges) {
if (edge.source.equals(t1) && edge.target.equals(t2)) {
return edge;
}
}
return null;
}
// 广度优先搜索,广度优先搜索其实是利用了一个队列
// 参考《算法导论》22.2节伪代码
public void printBFS(T t) {
Vertex vertex = isContainVertext(t);
if (vertex == null) {
throw new IllegalArgumentException("不能包含该节点,不能进行广度优先搜索!");
}
reSet();
System.out.println("广度优先搜索:");
vertex.color = Color.GRAY;
vertex.distance = 0;
Queue> queue = new LinkedList>();
queue.offer(vertex);
while (queue.size() > 0) {
Vertex u = queue.poll();
for (Vertex v : u.adjacencyVertices) {
if (v.color == Color.WHITE) {
v.color = Color.GRAY;
v.distance = u.distance + 1;
v.parent = u;
queue.offer(v);
}
}
u.color = Color.BLACK;
System.out.print(u);
System.out.print("→");
}
System.out.println();
}
public void printDFS(T t){
Vertex vertex = isContainVertext(t);
if (vertex == null) {
throw new IllegalArgumentException("不能包含该节点,不能进行广度优先搜索!");
}
reSet();
System.out.println("深度优先搜索:");
time=0;
dfsVisit(vertex);
for (Vertex u : vertices) {
if (u.equals(vertex)) {
continue;
}
if (u.color==Color.WHITE) {
dfsVisit(u);
}
}
System.out.println();
}
private void dfsVisit(Vertex u){
System.out.print(u);
System.out.print("→");
time++;
u.discover=time;
u.color=Color.GRAY;
for (Vertex v : u.adjacencyVertices) {
if (v.color==Color.WHITE) {
v.parent=u;
dfsVisit(v);
}
}
u.color=Color.BLACK;
time++;
u.finish=time;
topologicalStack.push(u);
}
/** * 打印出拓扑结构 * */
public void printTopology(T t){
if (topologicalStack.size()==0) {
printDFS(t);
}
while (topologicalStack.size()>0) {
System.out.print(topologicalStack.pop());
System.out.print("→");
}
System.out.println();
}
private void reSet(){
for (Vertex vertex : vertices) {
vertex.color=Color.WHITE;
}
topologicalStack.clear();
}
}
有向图和无向图仅仅添加边时不一样。
☆无向图添加边时,需要修改两个顶点双方的邻接矩阵和邻接表,而有向图添加边时,只修改起点的邻接矩阵和邻接表。
☆有向图单个定点可以有一条边指向自己,而无向图不能这样“自己指向自己”;
无向图如下,每次添加边时,都更新两个顶点对应的邻接表和邻接矩阵。
/** * * 无向Graph的实现 * 参考:《算法导论》22.1节. *
* * created by 曹艳丰 * 2016-09-05 *
* */
public class UndirectedGraph<T> extends Graph<T> {
@Override
public void addEdge(T source, T target) {
// TODO Auto-generated method stub
if (source.equals(target)) {
throw new IllegalArgumentException("无向图不能包含自旋!");
}
Vertex sourceVertex=isContainVertext(source);
Vertex targetVertex=isContainVertext(target);
if (sourceVertex==null) {
throw new IllegalArgumentException("不包含起始节点!");
}
if (targetVertex==null) {
throw new IllegalArgumentException("不包含终端节点!");
}
if (isContainEdge(source, target)!=null||isContainEdge(target,source)!=null) {
throw new IllegalArgumentException("重复添加该边!");
}
// 添加新的边
edges.add(new UndirectedEdge(source, target));
int row = 0, column = 0;
int counter = 0;
int i=0;
for (Vertex vertex : vertices) {
if (vertex.value.equals(source)) {
vertex.adjacencyVertices.add(targetVertex);//更新邻接表
row = i;
counter++;
if (counter == 2) {
setMatrixValue(row, column);// 设置邻接矩阵的值
setMatrixValue(column, row);// 设置邻接矩阵的值
break;
}
}else if (vertex.value.equals(target)) {
vertex.adjacencyVertices.add(sourceVertex);//更新邻接表
column = i;
counter++;
if (counter == 2) {
setMatrixValue(row, column);
setMatrixValue(column, row);
break;
}
}
i++;
}
}
}
有向图如下,每次添加边的时候,都更新起点的邻接表和邻接矩阵。
/** * * 有向Graph的实现 * 参考:《算法导论》22.1节. *
* * created by 曹艳丰 * 2016-09-05 *
* */
public class DirectedGraph<T> extends Graph<T> {
@Override
public void addEdge(T source, T target) {
// TODO Auto-generated method stub
Vertex sourceVertex=isContainVertext(source);
Vertex targetVertex=isContainVertext(target);
if (sourceVertex==null) {
throw new IllegalArgumentException("不包含起始节点!");
}
if (targetVertex==null) {
throw new IllegalArgumentException("不包含终端节点!");
}
if (isContainEdge(source, target)!=null) {
throw new IllegalArgumentException("重复添加该边!");
}
// 添加新的边
edges.add(new DirectedEdge(source, target));
int row = 0, column = 0;
int counter = 0;
int i=0;
for (Vertex vertex : vertices) {
if (vertex.value.equals(source)&&source.equals(target)) {
vertex.adjacencyVertices.add(targetVertex);//更新邻接表
row = i;
column=i;
setMatrixValue(row, column);// 设置邻接矩阵的值
break;
}else if (vertex.value.equals(source)) {
vertex.adjacencyVertices.add(targetVertex);//更新邻接表
row = i;
counter++;
if (counter == 2) {
setMatrixValue(row, column);// 设置邻接矩阵的值
break;
}
}else if (vertex.value.equals(target)) {
column = i;
counter++;
if (counter == 2) {
setMatrixValue(row, column);
break;
}
}
i++;
}
}
}
有了这几个类之后,可以进行一系列测试。
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
testUndirectedGraph();
testDirectedGraph();
testBFS();
testDFS();
testTopolgy();
}
/** * 《算法导论》22.1节图22.1 * */
private static void testUndirectedGraph(){
Graph graph=new UndirectedGraph();
graph.addVertex("1");
graph.addVertex("2");
graph.addVertex("3");
graph.addVertex("4");
graph.addVertex("5");
graph.addEdge("1","2");
graph.addEdge("5","1");
graph.addEdge("2","3");
graph.addEdge("2","4");
graph.addEdge("2","5");
graph.addEdge("4","3");
graph.addEdge("4","5");
graph.printAdjacencyMatrix();
graph.printAdjacencyVertices();
}
/** * 《算法导论》22.1节图22.1 * */
private static void testDirectedGraph(){
Graph graph2=new DirectedGraph();
graph2.addVertex(1).addVertex(2).addVertex(3).addVertex(4).addVertex(5).addVertex(6);
graph2.addEdge(1, 2);
graph2.addEdge(1, 4);
graph2.addEdge(2, 5);
graph2.addEdge(3, 5);
graph2.addEdge(3, 6);
graph2.addEdge(4, 2);
graph2.addEdge(5, 4);
graph2.addEdge(6, 6);
graph2.printAdjacencyMatrix();
graph2.printAdjacencyVertices();
}
/** * 《算法导论》22.2节图22.3 * * */
private static void testBFS(){
Graph graph=new UndirectedGraph();
graph.addVertex("s").addVertex("r").addVertex("v")
.addVertex("w").addVertex("t").addVertex("x").addVertex("u").addVertex("y");
graph.addEdge("v","r");
graph.addEdge("r","s");
graph.addEdge("s","w");
graph.addEdge("w","t");
graph.addEdge("w","x");
graph.addEdge("t","x");
graph.addEdge("t","u");
graph.addEdge("x","u");
graph.addEdge("x","y");
graph.addEdge("u","y");
graph.printBFS("x");
}
private static void testDFS(){
Graph graph2=new DirectedGraph();
graph2.addVertex("u").addVertex("v").addVertex("w").addVertex("x").addVertex("y").addVertex("z");
graph2.addEdge("u", "x");
graph2.addEdge("u", "v");
graph2.addEdge("x", "v");
graph2.addEdge("v", "y");
graph2.addEdge("y", "x");
graph2.addEdge("w", "y");
graph2.addEdge("w", "z");
graph2.addEdge("z", "z");
graph2.printDFS("u");
}
/** * 《算法导论》22.4节图22.7 * * */
private static void testTopolgy(){
Graph graph2=new DirectedGraph();
graph2.addVertex("undershorts").addVertex("pants").addVertex("belt").
addVertex("shirt").addVertex("tie").addVertex("jacket").addVertex("socks").
addVertex("watch").addVertex("shoes");
graph2.addEdge("undershorts", "pants");
graph2.addEdge("undershorts", "shoes");
graph2.addEdge("pants", "belt");
graph2.addEdge("belt", "jacket");
graph2.addEdge("pants", "shoes");
graph2.addEdge("shirt", "tie");
graph2.addEdge("shirt", "belt");
graph2.addEdge("tie", "jacket");
graph2.addEdge("socks", "shoes");
graph2.printTopology("shirt");
}
}
测试结果如下。
邻接矩阵形式:
0 1 0 0 1
1 0 1 1 1
0 1 0 1 0
0 1 1 0 1
1 1 0 1 0
邻接表形式:
1→2→5
2→1→3→4→5
3→2→4
4→2→3→5
5→1→2→4
邻接矩阵形式:
0 1 0 1 0 0
0 0 0 0 1 0
0 0 0 0 1 1
0 1 0 0 0 0
0 0 0 1 0 0
0 0 0 0 0 1
邻接表形式:
1→2→4
2→5
3→5→6
4→2
5→4
6→6
广度优先搜索:
x→w→t→u→y→s→r→v→
深度优先搜索:
u→x→v→y→w→z→
深度优先搜索:
shirt→tie→jacket→belt→undershorts→pants→shoes→socks→watch→
watch→socks→undershorts→pants→shoes→shirt→belt→tie→jacket→
注意:深度优先、广度优先以及拓扑排序与《算法导论》中的结果不完全一直,这是由邻接表中顶点的顺序不完全同书上一致导致的。例如,最后的拓扑排序为:
**watch→**socks→undershorts→pants→shoes→shirt→belt→tie→jacket→
而书上的为
socks→undershorts→pants→shoes→**watch→**shirt→belt→tie→jacket→
只有watch不一致,而watch放到哪里都是可以的。