邻接矩阵、邻接表

本文来源于liuyubobobo的“算法与数据结构--综合提升篇”视频教程

图的基本概念

先了解图的一些概念。

图在数学、代码中的实现方式,

一般来说使用邻接矩阵表示稠密图,使用邻接表表示稀疏图。下面会对邻接矩阵、邻接表加以说明。

实现代码:

// 稠密图 -- 邻接矩阵
public class DenseGraph {

    private int nodeTotal;  // 节点数
    private int nodeEdgeNum;  // 节点的边数
    private boolean directed;   // 是否为有向图
    private boolean[][] data2Arr;      // 图的具体数据

    // 构造函数
    public DenseGraph( int nodeTotal , boolean directed ){
        assert nodeTotal >= 0;
        this.nodeTotal = nodeTotal;
        this.nodeEdgeNum = 0;    // 初始化没有任何边
        this.directed = directed;
        // boolean型变量的默认值为false
        // 图的数据data2Arr初始化为nodeTotal*nodeTotal的布尔矩阵, 每一个data2Arr[i][j]均为false, 表示没有任何边
        data2Arr = new boolean[nodeTotal][nodeTotal];
    }

    public int getNodeTotal(){ return nodeTotal;} // 返回节点个数
    public int getNodeEdgeNum(){ return nodeEdgeNum;} // 返回节点边的个数

    // 向图中添加一个边
    public void addEdge(int node1, int node2){

        assert node1 >= 0 && node1 < nodeTotal;
        assert node2 >= 0 && node2 < nodeTotal;

        if( hasEdge(node1, node2) )
            return;

        data2Arr[node1][node2] = true;

        if( !directed ){
            //无向图,node2、node1也相连
            data2Arr[node2][node1] = true;
        }

        nodeEdgeNum++;
    }

    // 验证图中是否有从node1到node2的边
    public boolean hasEdge(int node1, int node2){
        assert node1 >= 0 && node1 < nodeTotal;
        assert node2 >= 0 && node2 < nodeTotal;
        return data2Arr[node1][node2];
    }

    // 显示图的信息
    public void show(){
        for(int i = 0; i < nodeTotal; i ++ ){
            for(int j = 0; j < nodeTotal; j ++ )
                System.out.print(data2Arr[i][j]+"\t");
            System.out.println();
        }
    }

    public static void main(String[] args) {
        DenseGraph dg = new DenseGraph(4, false);
        dg.addEdge(1,2);

        dg.show();
    }

}

实现代码

public class SparseGraph {

    private int nodeTotal;  // 节点数
    private int nodeEdgeNum;  // 节点的边数
    private boolean directed;   // 是否为有向图
    private Vector<Integer>[] dataArrVector; // 图的具体数据

    // 构造函数
    public SparseGraph( int nodeTotal , boolean directed ){
        assert nodeTotal >= 0;
        this.nodeTotal = nodeTotal;
        this.nodeEdgeNum = 0;    // 初始化没有任何边
        this.directed = directed;
        // dataArrVector初始化为nodeTotal个空的vector, 表示每一个dataArrVector[i]都为空, 即没有任何边
        dataArrVector = (Vector<Integer>[])new Vector[nodeTotal];
        for(int i = 0 ; i < nodeTotal ; i ++)
            dataArrVector[i] = new Vector<Integer>();
    }

    public int getNodeTotal(){ return nodeTotal;} // 返回节点个数
    public int getNodeEdgeNum(){ return nodeEdgeNum;} // 返回节点边的个数

    // 向图中添加一个边
    public void addEdge(int node1, int node2){

        assert node1 >= 0 && node1 < nodeTotal;
        assert node2 >= 0 && node2 < nodeTotal;

        dataArrVector[node1].add(node2);
        if( node1 != node2 && !directed )
            dataArrVector[node2].add(node1);

        nodeEdgeNum++;
    }

    // 验证图中是否有从node1到node2的边
    public boolean hasEdge(int node1, int node2){

        assert node1 >= 0 && node1 < nodeTotal;
        assert node2 >= 0 && node2 < nodeTotal;

        for(int i = 0; i < dataArrVector[node1].size() ; i ++ )
            if( dataArrVector[node1].elementAt(i) == node2)
                return true;
        return false;
    }

    // 显示图的信息
    public void show(){

        for(int i = 0; i < nodeTotal; i ++ ){
            System.out.print("vertex " + i + ":\t");
            for(int j = 0; j < dataArrVector[i].size() ; j ++ )
                System.out.print(dataArrVector[i].elementAt(j) + "\t");
            System.out.println();
        }
    }

    public static void main(String[] args) {
        SparseGraph dg = new SparseGraph(4, false);
        dg.addEdge(1,2);

        dg.show();
    }

}

从数据文件中读取一个图

一般来说,图中点、边的数据是记录在一个文件中的,下面创建三个TXT文件,记录图的点和边的关系。

下面给出两个数据文件

testG1.txt

13 13
0 5
4 3
0 1
9 12
6 4
5 4
0 2
11 12
9 10
0 6
7 8
9 11
5 3

testG2.txt

6 8
0 1
0 2
0 5
1 2
1 3
1 4
3 4
3 5

testG.txt

7 8
0 1
0 2
0 5
0 6
3 4
3 5
4 5
4 6

定义一个图的接口

// 图的接口
public interface Graph {

    public int getNodeTotal();
    public int getNodeEdgeNum();
    public void addEdge(int v, int w);
    boolean hasEdge(int v, int w);
    void show();
    public Iterable<Integer> getNodeEdges(int v);
}

创建读取图的类

/**
 * 从文件中读取一个图
 */
public class ReadGraph {

    private Scanner scanner;

    /**
     * 读取文件,创建一个图
     * @param graph
     * @param filename
     */
    public ReadGraph(Graph graph, String filename) {
        // 读取文件
        readFile(filename);

        try {
            // 文件第一行的两个数字是点数、边数
            int V = scanner.nextInt();
            if (V < 0)
                throw new IllegalArgumentException("定点数非负");
            assert V == graph.getNodeTotal();
            int E = scanner.nextInt();
            if (E < 0)
                throw new IllegalArgumentException("边数非负");

            // 从第二行开始,将数据添加到图对象中
            for (int i = 0; i < E; i++) {
                int v = scanner.nextInt();
                int w = scanner.nextInt();
                assert v >= 0 && v < V;
                assert w >= 0 && w < V;
                graph.addEdge(v, w);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void readFile(String filename) {
        assert filename != null;
        try {
            File file = new File(filename);
            if (file.exists()) {
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            } else{
                throw new IllegalArgumentException(filename + "doesn't exist.");
            }
        } catch (IOException ioe) {
            throw new IllegalArgumentException("Could not open " + filename, ioe);
        }
    }
}

图的深度遍历、连通分量

深度遍历、连通分量代码、深度遍历寻址:

// 稠密图 -- 邻接矩阵
public class DenseGraph implements Graph{

    private int nodeTotal;  // 节点数
    private int nodeEdgeNum;  // 节点的边数
    private boolean directed;   // 是否为有向图
    private boolean[][] data2Arr;      // 图的具体数据

    // 构造函数
    public DenseGraph(int nodeTotal , boolean directed ){
        assert nodeTotal >= 0;
        this.nodeTotal = nodeTotal;
        this.nodeEdgeNum = 0;    // 初始化没有任何边
        this.directed = directed;
        // boolean型变量的默认值为false
        // 图的数据data2Arr初始化为nodeTotal*nodeTotal的布尔矩阵, 每一个data2Arr[i][j]均为false, 表示没有任何边
        data2Arr = new boolean[nodeTotal][nodeTotal];
    }

    public int getNodeTotal(){ return nodeTotal;} // 返回节点个数
    public int getNodeEdgeNum(){ return nodeEdgeNum;} // 返回节点边的个数

    // 向图中添加一个边
    public void addEdge(int node1, int node2){

        assert node1 >= 0 && node1 < nodeTotal;
        assert node2 >= 0 && node2 < nodeTotal;

        if( hasEdge(node1, node2) )
            return;

        data2Arr[node1][node2] = true;

        if( !directed ){
            //无向图,node2、node1也相连
            data2Arr[node2][node1] = true;
        }

        nodeEdgeNum++;
    }

    // 验证图中是否有从node1到node2的边
    public boolean hasEdge(int node1, int node2){
        assert node1 >= 0 && node1 < nodeTotal;
        assert node2 >= 0 && node2 < nodeTotal;
        return data2Arr[node1][node2];
    }

    // 显示图的信息
    public void show(){
        for(int i = 0; i < nodeTotal; i ++ ){
            for(int j = 0; j < nodeTotal; j ++ )
                System.out.print(data2Arr[i][j]+"\t");
            System.out.println();
        }
    }

    /**
     * 获取相邻节点
     * @param node
     * @return
     */
    public Iterable<Integer> adjacentNode(int node){
        assert node >= 0 && node < nodeTotal;
        Vector<Integer> nodeVector = new Vector<>();
        // data2Arr[node]是一个数组,遍历此数组
        for (int i=0; i<nodeTotal; i++){
            // data2Arr[node][i]为true,则node和数值为i的节点相连
            if (data2Arr[node][i]){
                // 将节点i添加到数组中
                nodeVector.add(i);
            }
        }
        return nodeVector;
    }
}
// 稀疏图 -- 邻接表
public class SparseGraph implements Graph{

    private int nodeTotal;  // 节点数
    private int nodeEdgeNum;  // 节点的边数
    private boolean directed;   // 是否为有向图
    private Vector<Integer>[] dataArrVector; // 图的具体数据

    // 构造函数
    public SparseGraph(int nodeTotal , boolean directed ){
        assert nodeTotal >= 0;
        this.nodeTotal = nodeTotal;
        this.nodeEdgeNum = 0;    // 初始化没有任何边
        this.directed = directed;
        // dataArrVector初始化为nodeTotal个空的vector, 表示每一个dataArrVector[i]都为空, 即没有任何边
        dataArrVector = (Vector<Integer>[])new Vector[nodeTotal];
        for(int i = 0 ; i < nodeTotal ; i ++)
            dataArrVector[i] = new Vector<Integer>();
    }

    public int getNodeTotal(){ return nodeTotal;} // 返回节点个数
    public int getNodeEdgeNum(){ return nodeEdgeNum;} // 返回节点边的个数

    // 向图中添加一个边
    public void addEdge(int node1, int node2){

        assert node1 >= 0 && node1 < nodeTotal;
        assert node2 >= 0 && node2 < nodeTotal;

        dataArrVector[node1].add(node2);
        if( node1 != node2 && !directed )
            dataArrVector[node2].add(node1);

        nodeEdgeNum++;
    }

    // 验证图中是否有从node1到node2的边
    public boolean hasEdge(int node1, int node2){

        assert node1 >= 0 && node1 < nodeTotal;
        assert node2 >= 0 && node2 < nodeTotal;

        for(int i = 0; i < dataArrVector[node1].size() ; i ++ )
            if( dataArrVector[node1].elementAt(i) == node2)
                return true;
        return false;
    }

    // 显示图的信息
    public void show(){

        for(int i = 0; i < nodeTotal; i ++ ){
            System.out.print("vertex " + i + ":\t");
            for(int j = 0; j < dataArrVector[i].size() ; j ++ )
                System.out.print(dataArrVector[i].elementAt(j) + "\t");
            System.out.println();
        }
    }


    /**
     * 获取相邻节点
     * @param node
     * @return
     */
    public Iterable<Integer> adjacentNode(int node){
        assert node >= 0 && node < nodeTotal;
        // 邻接表,dataArrVector[node]中的节点都和node相连接
        return dataArrVector[node];
    }
}
// 连通分量
public class Components {

    Graph G;                    // 图的引用
    private boolean[] visited; // 记录深度遍历的过程中节点是否被访问
    private int count;         // 记录连通分量个数
    private int[] id;           // 每个节点所对应的连通分量标记

    // 构造函数,求出无权图的连通分量
    public Components(Graph graph){

        // 算法初始化
        G = graph;
        // visited数组长度是图节点的个数
        visited = new boolean[G.getNodeTotal()];
        id = new int[G.getNodeTotal()];
        count = 0;
        for( int i = 0 ; i < G.getNodeTotal() ; i ++ ){
            // 刚才开始,所有节点都没遍历
            visited[i] = false;
            // 刚才开始,每个节点不属于任何连通分量
            id[i] = -1;
        }

        // 求图的连通分量
        for( int i = 0 ; i < G.getNodeTotal() ; i ++ )
            if( !visited[i] ){
                // i未遍历,深度遍历i
                deepFirstSearch(i);
                // i深度遍历完了,连通分量++
                count++;
            }
    }

    // 图的深度优先遍历
    void deepFirstSearch(int node){

        // 当前节点设置为已经遍历
        visited[node] = true;
        // 当前节点属于连通分量count
        id[node] = count;

        // 深度遍历当前节点的相邻节点
        for( int i: G.adjacentNode(node) ){
            if( !visited[i] )
                deepFirstSearch(i);
        }
    }

    // 返回图的连通分量个数
    int count(){
        return count;
    }

    // 查询点node1和点node2是否连通
    boolean isConnected(int node1, int node2){
        assert node1 >= 0 && node1 < G.getNodeTotal();
        assert node2 >= 0 && node2 < G.getNodeTotal();
        return id[node1] == id[node2];
    }

}
// 深度遍历寻址
public class Path {

    private Graph G;   // 图的引用
    private int startNode;     // 起始点
    private boolean[] visited;  // 记录深度遍历的过程中节点是否被访问
    private int[] from;         // 记录路径, from[i]表示查找的路径上i的上一个节点

    // 构造函数, 寻路算法, 寻找图graph从startNode点到其他点的路径
    public Path(Graph graph, int startNode){

        // 算法初始化
        G = graph;
        assert startNode >= 0 && startNode < G.getNodeTotal();

        visited = new boolean[G.getNodeTotal()];
        from = new int[G.getNodeTotal()];
        for( int i = 0 ; i < G.getNodeTotal() ; i ++ ){
            visited[i] = false;
            from[i] = -1;
        }
        this.startNode = startNode;

        // 寻路算法
        deepFirstSearch(startNode);
    }


    // 图的深度优先遍历
    private void deepFirstSearch(int node ){
        visited[node] = true;
        // node的相邻节点
        for( int i : G.adjacentNode(node) )
            // 相邻节点未被遍历过
            if( !visited[i] ){
                // from[i]位置存储上一个节点node
                from[i] = node;
                deepFirstSearch(i);
            }
    }

    // 查询从startNode点到targetNode点是否有路径
    boolean hasPath(int targetNode){
        assert targetNode >= 0 && targetNode < G.getNodeTotal();
        return visited[targetNode];
    }

    // 查询从s点到w点的路径, 存放在vec中
    Vector<Integer> path(int targetNode){

        assert hasPath(targetNode) ;

        // 栈,先进后出
        Stack<Integer> s = new Stack<Integer>();
        // 通过from数组逆向查找到从startNode到targetNode的路径, 存放到栈中
        int p = targetNode;
        // p不为-1,即节点p有上一个节点,上一个节点存放在from[p]中
        while( p != -1 ){
            s.push(p);
            p = from[p];
        }

        // 从栈中依次取出元素,获得顺序的从startNode到targetNode的路径
        Vector<Integer> res = new Vector<Integer>();
        while( !s.empty() )
            res.add( s.pop() );

        return res;
    }

    // 打印出从startNode到targetNode的路径
    void showPath(int targetNode){

        assert hasPath(targetNode) ;

        Vector<Integer> vec = path(targetNode);
        for( int i = 0 ; i < vec.size() ; i ++ ){
            System.out.print(vec.elementAt(i));
            if( i == vec.size() - 1 )
                System.out.println();
            else
                System.out.print(" -> ");
        }
    }
}
// 测试图的联通分量、深度遍历寻址
public class Main {

    public static void main(String[] args) {

        //// TestG1.txt
        //String filename1 = "note\\src\\main\\java\\com\\datastructure_arithmetic\\testG1.txt";
        //SparseGraph g1 = new SparseGraph(13, false);
        //ReadGraph readGraph1 = new ReadGraph(g1, filename1);
        //Components component1 = new Components(g1);
        //System.out.println("TestG1.txt, 连通分量: " + component1.count());
        //System.out.println();
        //
        //// TestG2.txt
        //String filename2 = "note\\src\\main\\java\\com\\datastructure_arithmetic\\testG2.txt";
        //SparseGraph g2 = new SparseGraph(6, false);
        //ReadGraph readGraph2 = new ReadGraph(g2, filename2);
        //Components component2 = new Components(g2);
        //System.out.println("TestG2.txt, 连通分量: " + component2.count());


        String filename1 = "note\\src\\main\\java\\com\\datastructure_arithmetic\\testG1.txt";
        SparseGraph g = new SparseGraph(13, false);
        ReadGraph readGraph = new ReadGraph(g, filename1);
        g.show();
        System.out.println();

        Path path = new Path(g,0);
        System.out.println("Path from 0 to 6 : ");
        path.showPath(6);
    }
}

无权图:两个节点之间连线没有长短之分,两点之间连接则为1,不连接则为0。

使用队列可以实现图的广度遍历,广度遍历可以求出无权图的最短路径

// 广度优先遍历
public class ShortestPath {

    private Graph G;   // 图的引用
    private int startNode;     // 起始点
    private boolean[] visited;  // 记录dfs的过程中节点是否被访问
    private int[] from;         // 记录路径, from[i]表示查找的路径上i的上一个节点
    private int[] ord;          // 记录路径中节点的次序。ord[i]表示i节点在路径中的次序。


    // 构造函数, 寻路算法, 寻找图graph从startNode点到其他点的路径
    public ShortestPath(Graph graph, int startNode){

        // 算法初始化
        G = graph;
        assert startNode >= 0 && startNode < G.getNodeTotal();

        visited = new boolean[G.getNodeTotal()];
        from = new int[G.getNodeTotal()];
        ord = new int[G.getNodeTotal()];
        for( int i = 0 ; i < G.getNodeTotal() ; i ++ ){
            visited[i] = false;
            from[i] = -1;
            ord[i] = -1;
        }
        this.startNode = startNode;

        // 无向图最短路径算法, 从startNode开始广度优先遍历整张图
        Queue<Integer> q = new LinkedList<Integer>();

        q.add(startNode);
        visited[startNode] = true;
        ord[startNode] = 0;
        while( !q.isEmpty() ){
            int v = q.remove();
            for( int i : G.adjacentNode(v) )
                if( !visited[i] ){
                    q.add(i);
                    visited[i] = true;
                    from[i] = v;
                    ord[i] = ord[v] + 1;
                }
        }
    }

    // 查询从startNode点到target点是否有路径
    public boolean hasPath(int target){
        assert target >= 0 && target < G.getNodeTotal();
        return visited[target];
    }

    // 查询从startNode点到target点的路径, 存放在vec中
    public Vector<Integer> path(int target){

        assert hasPath(target) ;

        Stack<Integer> s = new Stack<Integer>();
        // 通过from数组逆向查找到从startNode到target的路径, 存放到栈中
        int p = target;
        while( p != -1 ){
            s.push(p);
            p = from[p];
        }

        // 从栈中依次取出元素, 获得顺序的从startNode到target的路径
        Vector<Integer> res = new Vector<Integer>();
        while( !s.empty() )
            res.add( s.pop() );

        return res;
    }

    // 打印出从startNode点到target点的路径
    public void showPath(int target){

        assert hasPath(target) ;

        Vector<Integer> vec = path(target);
        for( int i = 0 ; i < vec.size() ; i ++ ){
            System.out.print(vec.elementAt(i));
            if( i == vec.size() - 1 )
                System.out.println();
            else
                System.out.print(" -> ");
        }
    }

    // 查看从startNode点到target点的最短路径长度
    // 若从startNode到target不可达,返回-1
    public int length(int w){
        assert w >= 0 && w < G.getNodeTotal();
        return ord[w];
    }
}
发布了51 篇原创文章 · 获赞 14 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u010606397/article/details/102776728
今日推荐