Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

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

$
0
0

前言

之前的博客“【算法导论-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 
邻接表形式:
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放到哪里都是可以的。

作者:BrilliantEagle 发表于2016/9/9 20:11:25 原文链接
阅读:73 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>