【算法导论-37】Graph的Java实现

前言

之前的博客“【算法导论-35】图算法JGraphT开源库介绍”中提到的开源版本的Graph库。然而,继续《算法导论》的学习必须自己实现Graph。所以,放弃使用该库,实现自己的Graph类。
注意,本篇博客紧密结合《算法导论》第22章,深度优先、广度优先、拓扑排序算法都取自相关章节的伪代码,这里不再讲解相关原理。

Graph的实现

基础的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++; } } }

Graph测试

有了这几个类之后,可以进行一系列测试。

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 
邻接表形式:
125
21345
324
4235
5124
邻接矩阵形式:
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 
邻接表形式:
124
25
356
42
54
66
广度优先搜索:
x→w→t→u→ys→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放到哪里都是可以的。

你可能感兴趣的:(《算法导论》学习)