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

来源:互联网 发布:青少年性犯罪数据 编辑:程序博客网 时间:2024/05/04 17:02

前言

之前的博客“【算法导论-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;/** * <p> * Graph的顶点 参考:《算法导论》22.2节. * <p/> * <p> * created by 曹艳丰 2016-09-05 * <p/> * */public class Vertex<T> {    public Color color;    public int distance;    public Vertex<T> parent;    public T value;    public int discover,finish;//深度优先搜索的第一次发现和结束的时间    public Set<Vertex<T>> adjacencyVertices;// 邻接表,采用集合来表示    public Vertex(T t) {        // TODO Auto-generated constructor stub        color = Color.WHITE;        distance = Integer.MAX_VALUE;        parent = null;        value = t;        adjacencyVertices = new LinkedHashSet<Vertex<T>>();        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。

/** * <p> * Graph的顶点 * 参考:《算法导论》22.2节. * <p/> * <p> * created by 曹艳丰 * 2016-09-05 * <p/> * */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。

/** * <p> * 无向Graph的Edge实现 * <p/> * <p> * created by 曹艳丰 * 2016-09-05 * <p/> * */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。

/** * <p> * 有向Graph的Edge实现 * <p/> * <p> * created by 曹艳丰 * 2016-09-05 * <p/> * */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;/** * <p> * Graph的实现 * <p/> * <p> * 参考:《算法导论》22.1节. * <p/> * <p> * created by 曹艳丰 * <p/> * <p> * 2016-09-05 * <p/> * */public abstract class Graph<T> {    protected int[][] adjacencyMatrix;// 邻接矩阵    protected int size = 0;// 定点总数    public Set<Vertex<T>> vertices;// 所有节点    public Set<Edge<T>> edges;// 所有边    private int time;    private Stack<Vertex<T>> topologicalStack;//用于拓扑排序    public Graph() {        // TODO Auto-generated constructor stub        adjacencyMatrix = new int[size][size];        vertices = new LinkedHashSet<Vertex<T>>();        edges = new LinkedHashSet<Edge<T>>();        topologicalStack=new Stack<Vertex<T>>();    }    public Graph<T> addVertex(T t) {        Vertex<T> vertex = new Vertex<T>(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<T> vertex : vertices) {            System.out.print(vertex);            for (Vertex<T> vertex2 : vertex.adjacencyVertices) {                System.out.print("→");                System.out.print(vertex2);            }            System.out.println();        }    }    protected Vertex<T> isContainVertext(T t) {        for (Vertex<T> v : vertices) {            if (v.value.equals(t)) {                return v;            }        }        return null;    }    protected Edge<T> isContainEdge(T t1, T t2) {        for (Edge<T> edge : edges) {            if (edge.source.equals(t1) && edge.target.equals(t2)) {                return edge;            }        }        return null;    }    // 广度优先搜索,广度优先搜索其实是利用了一个队列    // 参考《算法导论》22.2节伪代码    public void printBFS(T t) {        Vertex<T> vertex = isContainVertext(t);        if (vertex == null) {            throw new IllegalArgumentException("不能包含该节点,不能进行广度优先搜索!");        }        reSet();        System.out.println("广度优先搜索:");        vertex.color = Color.GRAY;        vertex.distance = 0;        Queue<Vertex<T>> queue = new LinkedList<Vertex<T>>();        queue.offer(vertex);        while (queue.size() > 0) {            Vertex<T> u = queue.poll();            for (Vertex<T> 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<T> vertex = isContainVertext(t);        if (vertex == null) {            throw new IllegalArgumentException("不能包含该节点,不能进行广度优先搜索!");        }        reSet();        System.out.println("深度优先搜索:");        time=0;        dfsVisit(vertex);        for (Vertex<T> u : vertices) {            if (u.equals(vertex)) {                continue;            }            if (u.color==Color.WHITE) {                dfsVisit(u);            }        }        System.out.println();    }    private void dfsVisit(Vertex<T> u){        System.out.print(u);        System.out.print("→");        time++;        u.discover=time;        u.color=Color.GRAY;        for (Vertex<T> 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<T> vertex : vertices) {            vertex.color=Color.WHITE;        }        topologicalStack.clear();    }}

有向图和无向图仅仅添加边时不一样。
☆无向图添加边时,需要修改两个顶点双方的邻接矩阵和邻接表,而有向图添加边时,只修改起点的邻接矩阵和邻接表。
☆有向图单个定点可以有一条边指向自己,而无向图不能这样“自己指向自己”;
无向图如下,每次添加边时,都更新两个顶点对应的邻接表和邻接矩阵。

/** * <p> * 无向Graph的实现 * 参考:《算法导论》22.1节. * <p/> * <p> * created by 曹艳丰 * 2016-09-05 * <p/> * */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<T> sourceVertex=isContainVertext(source);        Vertex<T> 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<T>(source, target));        int row = 0, column = 0;        int counter = 0;        int i=0;        for (Vertex<T> 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++;        }    }}

有向图如下,每次添加边的时候,都更新起点的邻接表和邻接矩阵。

/** * <p> * 有向Graph的实现 * 参考:《算法导论》22.1节. * <p/> * <p> * created by 曹艳丰 * 2016-09-05 * <p/> * */public class DirectedGraph<T> extends Graph<T> {    @Override    public void addEdge(T source, T target) {        // TODO Auto-generated method stub        Vertex<T> sourceVertex=isContainVertext(source);        Vertex<T> 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<T>(source, target));        int row = 0, column = 0;        int counter = 0;        int i=0;        for (Vertex<T> 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<String> graph=new UndirectedGraph<String>();        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<Integer> graph2=new DirectedGraph<Integer>();        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<String> graph=new UndirectedGraph<String>();        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<String> graph2=new DirectedGraph<String>();        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<String> graph2=new DirectedGraph<String>();        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 邻接表形式:1252134532442355124邻接矩阵形式: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 邻接表形式:12425356425466广度优先搜索: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放到哪里都是可以的。

0 0