【图】概念、存储结构、广度优先遍历遍历、深度优先遍历 - 详解

目录

前言

一、图

1.1、基本概念

二、图的存储结构

2.1、存储结构

2.1、邻接矩阵(考察重点)

2.1.1、代码实现

2.2、邻接表

2.3.1、无向邻接表存储

2.3.2、有向图邻接表存储

3.1、图的广度优先遍历(层序遍历)

3.2、图的深度优先遍历


前言


本章主要讲的是图的基本概念以及应用,面试的时候基本不考图~

一、图

1.1、基本概念

图是由顶点集合及顶点间的关系组成的一种数据结构:G = (V, E),具体的:

  • 顶点集合V = {x|x属于某个数据对象集}是有穷非空集合;
  • E = {(x,y)|x,y属于V}或者E = {|x,y属于V && Path(x, y)}是顶点间关系的有穷集合,也叫做边的集 合。

(x, y)表示 x 到 y 的一条双向路径,即(x, y)是无方向的;Path<x, y>表示从x到y的一条单向通路,即Path是有方向的。例如下图:

顶点和边:图中结点称为顶点,第i个顶点记作vi。两个顶点vi和vj相关联称作顶点vi和顶点vj之间有一条边, 图中的第k条边记作ek,ek = (vi,vj)或,<vi, vj>。例如下图:

有向图和无向图:

  • 在有向图中,顶点对是有序的,顶点对,y>称为顶点x到顶点y的一条边(弧),和是两条不同的边;
  • 在无向图中,顶点对(x, y)是无序的,顶点对(x,y)称为顶点x和顶点y相关联的一条边,这条边没有特定方向,(x,  y)和(y,x)是同一条边;

例如下图:

完全图:

  • 在有n个顶点的无向图中,若有n * (n - 1) / 2条边,即任意两个顶点之间有且仅有一条边,则称此图为 无向完全图;
  • 在n个顶点的有向图中,若有n * (n-1)条边,即任意两个顶点之间有且仅有方向 相反的边,则称此图为有向完全图;

例如下图:

邻接顶点:

  • 在无向图中G中,若(u, v)是E(G)中的一条边,则称u和v互为邻接顶点,并称边(u,v)依附于顶点u和v;
  • 在有向图G中,若是E(G)中的一条边,则称顶点u邻接到v,顶点v邻接自顶点u,并称边与顶点u和顶点v相关联。

顶点的度:

顶点v的度是指与它相关联的边的条数(类似树的度),记作deg(v)。在有向图中,顶点的度等于该顶点的入度与 出度之和,其中顶点v的入度是以v为终点的有向边的条数,记作indev(v);顶点v的出度是以v为起始点的有向 边的条数,记作outdev(v)。因此:dev(v) = indev(v) + outdev(v)。注意:对于无向图,顶点的度等于该顶 点的入度和出度,即dev(v) = indev(v) = outdev(v)。

路径:在图G = (V, E)中,若从顶点vi出发有一组边使其可到达顶点vj,则称顶点vi到顶点vj的顶点序列为从 顶点vi到顶点vj的路径。

路径长度:

  • 对于不带权的图,一条路径的路径长度是指该路径上的边的条数;
  • 对于带权的图,一条路径的路 径长度是指该路径上各个边权值的总和。

例如下图:

简单路径与回路:若路径上各顶点v1,v2,v3,…,vm均不重复,则称这样的路径为简单路径。回路或环:路径上第一个顶点v1和最后一个顶点vm重合。

例如下图:

 子图:设图G = {V, E}和图G1 = {V1,E1},若V1属于V且E1属于E,则称G1是G的子图

例如下图:

连通图:无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果图中任意一对顶点都是连通的,则称此图为连通图。

例如下图:

强连通图:有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到 vi的路 径,则称此图是强连通图。

例如下图:

生成树:一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n-1条 边。

二、图的存储结构


2.1、存储结构

在图的存储中,只需要保存:节点和边的关系即可~

2.1、邻接矩阵(考察重点)

邻接矩阵就是用矩阵(二维数组)来表示图的节点和边的关系,其中用 1 表示连通,0表示不连通.

如下无权值:

如下有权值:

2.1.1、代码实现

import java.util.Arrays;

public class GraphByMatrix {

    private char[] arrayV;//顶点数组
    private int[][] matrix;//邻接矩阵
    private boolean isDirect;//是否是有向图

    /**
     * @param size 代表当前顶点的个数
     * @param isDirect
     */
    public GraphByMatrix(int size, boolean isDirect) {
        this.arrayV = new char[size];
        matrix = new int[size][size];
        //这里约定:统一初始化为最大值
        for(int i = 0; i < size; i++) {
            Arrays.fill(matrix[i], Constant.MAX);
        }
        this.isDirect = isDirect;
    }

    /**
     * 初始化顶点
     * @param array
     */
    public void initArrayV(char[] array) {
        for(int i = 0; i < array.length; i++) {
            arrayV[i] = array[i];
        }
    }

    /**
     * 增加边
     * @param srcV 起点
     * @param destV 终点
     * @param weight 权值
     */
    public void addEdge(char srcV, char destV, int weight) {
        int srcIndex = getIndexOfV(srcV);
        int destIndex = getIndexOfV(destV);

        matrix[srcIndex][destIndex] = weight;
        //如果是无向图,那么相反的位置,也同样需要置为空
        if(!isDirect) {
            matrix[destIndex][srcIndex] = weight;
        }
    }

    /**
     * 获取顶点的下标
     * @param v
     * @return
     */
    private int getIndexOfV(char v) {
        for(int i = 0; i < arrayV.length; i++) {
            if(arrayV[i] == v)
            return i;
        }
        return -1;
    }

    /**
     * 获取顶点的度,有向图 = 入度 + 出度
     * @return
     */
    public int getDevofV(char v) {
        int count = 0;
        int srcIndex = getIndexOfV(v);
        for(int i = 0; i < arrayV.length; i++) {
            if(matrix[srcIndex][i] != Constant.MAX) {
                count++;
            }
        }

        //计算有向图的入度
        if(isDirect) {
            for(int i = 0; i < arrayV.length; i++) {
                if(matrix[i][srcIndex] != Constant.MAX) {
                    count++;
                }
            }
        }
        return count;
    }

    //打印数组
    public void printGraph() {
        for(int i = 0; i < matrix.length; i++) {
            for(int j = 0; j < matrix[i].length; j++) {
                if(matrix[i][j] == Constant.MAX) {
                    System.out.print("∞ ");
                } else {
                    System.out.print(matrix[i][j] + " ");
                }
            }
            System.out.println();
        }
    }

    //测试
    public static void main(String[] args) {
        GraphByMatrix graph = new GraphByMatrix(4, true);
        char[] array = {'A', 'B', 'C', 'D'};
        graph.initArrayV(array);

        graph.addEdge('A', 'B', 1);
        graph.addEdge('A', 'D', 1);
        graph.addEdge('B', 'A', 1);
        graph.addEdge('B', 'C', 1);
        graph.addEdge('C', 'B', 1);
        graph.addEdge('C', 'D', 1);
        graph.addEdge('D', 'A', 1);
        graph.addEdge('D', 'C', 1);

        graph.printGraph();
        //获取 A 的度
        System.out.println(graph.getDevofV('A'));
    }

}

 

2.2、邻接表

邻接表:使用数组表示顶点的集合,使用链表表示边的关系 。

2.3.1、无向邻接表存储

Ps:无向图中同一条边在邻接表中出现了两次。如果想知道顶点vi的度,只需要知道顶点vi边链表集 合中结点的数目即可。 

2.3.2、有向图邻接表存储

Ps:有向图中每条边在邻接表中只出现一次,与顶点vi对应的邻接表所含结点的个数,就是该顶点的 出度,也称出度表,要得到vi顶点的入度,必须检测其他所有顶点对应的边链表,看有多少边顶点的dst取值是i。

import java.util.ArrayList;

public class GraphByNode {

    static class Node {
        public int src;//起始位置
        public int dest;//目标位置
        public int weight;//权重
        public Node next;

        public Node(int src, int dest, int weight) {
            this.src = src;
            this.dest = dest;
            this.weight = weight;
        }
    }

    public char[] arrayV;
    public ArrayList<Node> edgList;//存储边
    public boolean isDirect;

    public GraphByNode(int size, boolean isDirect) {
        this.arrayV = new char[size];
        edgList = new ArrayList<>(size);
        for(int i = 0; i < size; i++) {
            edgList.add(null);
        }
        this.isDirect = isDirect;
    }

    /**
     * 初始化顶点
     * @param array
     */
    public void initArrayV(char[] array) {
        for(int i = 0; i < array.length; i++) {
            arrayV[i] = array[i];
        }
    }

    /**
     * 增加边
     * @param srcV 起点
     * @param destV 终点
     * @param weight 权值
     */
    public void addEdge(char srcV, char destV, int weight) {
        int srcIndex = getIndexOfV(srcV);
        int destIndex = getIndexOfV(destV);
        addEdgeChild(srcIndex, destIndex, weight);

        //无向图需要添加两条边
        if(!isDirect) {
            addEdgeChild(destIndex, srcIndex, weight);
        }
    }

    private void addEdgeChild(int srcIndex, int destIndex, int weight) {
        //这里拿到的是头结点
        Node cur = edgList.get(srcIndex);
        while(cur != null) {
            if(cur.dest == destIndex) {
                return;
            }
            cur = cur.next;
        }
        //之前没有存储过这条边
        Node node = new Node(srcIndex, destIndex, weight);
        node.next = edgList.get(srcIndex);
        edgList.set(srcIndex, node);
    }

    /**
     * 获取顶点的下标
     * @param v
     * @return
     */
    private int getIndexOfV(char v) {
        for(int i = 0; i < arrayV.length; i++) {
            if(arrayV[i] == v)
                return i;
        }
        return -1;
    }

    //获取顶点的度
    public int getDevOfv(char v) {
        int count = 0;
        int srcIndex = getIndexOfV(v);
        Node cur = edgList.get(srcIndex);
        while(cur != null) {
            count++;
            cur = cur.next;
        }

        //有向图
        if(isDirect) {
            int destIndex = srcIndex;
            for(int i = 0; i < arrayV.length; i++) {
                if(i == destIndex) {
                    continue;
                } else {
                    Node pCur = edgList.get(i);
                    while(pCur != null) {
                        if(pCur.dest == destIndex) {
                            count++;
                        }
                        pCur = pCur.next;
                    }
                }
            }
        }
        return count;
    }


    public void printGraph() {
        for(int i = 0; i < arrayV.length; i++) {
            System.out.print(arrayV[i] + "->");
            Node cur = edgList.get(i);
            while(cur != null) {
                System.out.print(arrayV[cur.dest] + " ->");
                cur = cur.next;
            }
            System.out.println();
        }
    }

    //测试
    public static void main(String[] args) {
        GraphByNode graph = new GraphByNode(4, true);
        char[] array = {'A', 'B', 'C', 'D'};
        graph.initArrayV(array);

        graph.addEdge('A', 'B', 1);
        graph.addEdge('A', 'D', 1);
        graph.addEdge('B', 'A', 1);
        graph.addEdge('B', 'C', 1);
        graph.addEdge('C', 'B', 1);
        graph.addEdge('C', 'D', 1);
        graph.addEdge('D', 'A', 1);
        graph.addEdge('D', 'C', 1);
        System.out.println("getDevOfV:" + graph.getDevOfv('A'));
        graph.printGraph();
    }

}

3.1、图的广度优先遍历(层序遍历)

 类似于二叉树的层序遍历,也需要使用队列,首先将第一个元素(假设如上图中的B)放入队列,同时使用一个 boolean 类型数组标记这个元素已被放入(这样做是为了防止一个元素被多次放入),接着出队(将 B 弹出),将出队的元素的连通的结点(A,C结点)经检验不重复后(boolean 类型数组检验是否之前被放入过),再次放入队列,依次往复即可~


import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

public class GraphByMatrix {

    private char[] arrayV;//顶点数组
    private int[][] matrix;//邻接矩阵
    private boolean isDirect;//是否是有向图

    /**
     * @param size 代表当前顶点的个数
     * @param isDirect
     */
    public GraphByMatrix(int size, boolean isDirect) {
        this.arrayV = new char[size];
        matrix = new int[size][size];
        //这里约定:统一初始化为最大值
        for(int i = 0; i < size; i++) {
            Arrays.fill(matrix[i], Constant.MAX);
        }
        this.isDirect = isDirect;
    }

    /**
     * 初始化顶点
     * @param array
     */
    public void initArrayV(char[] array) {
        for(int i = 0; i < array.length; i++) {
            arrayV[i] = array[i];
        }
    }

    /**
     * 增加边
     * @param srcV 起点
     * @param destV 终点
     * @param weight 权值
     */
    public void addEdge(char srcV, char destV, int weight) {
        int srcIndex = getIndexOfV(srcV);
        int destIndex = getIndexOfV(destV);

        matrix[srcIndex][destIndex] = weight;
        //如果是无向图,那么相反的位置,也同样需要置为空
        if(!isDirect) {
            matrix[destIndex][srcIndex] = weight;
        }
    }

    /**
     * 获取顶点的下标
     * @param v
     * @return
     */
    private int getIndexOfV(char v) {
        for(int i = 0; i < arrayV.length; i++) {
            if(arrayV[i] == v)
            return i;
        }
        return -1;
    }

    /**
     * 获取顶点的度,有向图 = 入度 + 出度
     * @return
     */
    public int getDevofV(char v) {
        int count = 0;
        int srcIndex = getIndexOfV(v);
        for(int i = 0; i < arrayV.length; i++) {
            if(matrix[srcIndex][i] != Constant.MAX) {
                count++;
            }
        }

        //计算有向图的入度
        if(isDirect) {
            for(int i = 0; i < arrayV.length; i++) {
                if(matrix[i][srcIndex] != Constant.MAX) {
                    count++;
                }
            }
        }
        return count;
    }

    //打印数组
    public void printGraph() {
        for(int i = 0; i < matrix.length; i++) {
            for(int j = 0; j < matrix[i].length; j++) {
                if(matrix[i][j] == Constant.MAX) {
                    System.out.print("∞ ");
                } else {
                    System.out.print(matrix[i][j] + " ");
                }
            }
            System.out.println();
        }
    }


    //广度优先遍历
    public void bfs(char v) {
        //数组用来标记顶点是否被使用过
        boolean[] visited = new boolean[arrayV.length];

        //定义一个队列,来辅助完成 bfs
        Queue<Integer> queue = new LinkedList<>();
        int srcIndex = getIndexOfV(v);
        queue.offer(srcIndex);
        while(!queue.isEmpty()) {
            int top = queue.poll();
            System.out.print(arrayV[top] + "->");
            visited[top] = true;//每次弹出一个元素就置为 true
            for(int i = 0; i < arrayV.length; i++) {
                if(matrix[top][i] != Constant.MAX && !visited[i]) {
                    queue.offer(i);
                    visited[i] = true;//每次入队就置为true
                }
            }
        }
    }

    //测试
    public static void main(String[] args) {
        GraphByMatrix graph = new GraphByMatrix(4, false);
        char[] array = {'A', 'B', 'C', 'D'};
        graph.initArrayV(array);

        graph.addEdge('A', 'B', 1);
        graph.addEdge('A', 'D', 1);
        graph.addEdge('B', 'A', 1);
        graph.addEdge('B', 'C', 1);
        graph.addEdge('C', 'B', 1);
        graph.addEdge('C', 'D', 1);
        graph.addEdge('D', 'A', 1);
        graph.addEdge('D', 'C', 1);

        graph.bfs('B');
    }

}

3.2、图的深度优先遍历

 

类似于二叉树的前序遍历,通过邻接矩阵进行优先遍历,例如起始位置为上图中的 B,那么上图中右边的邻接矩阵从竖列 B 元素开始向右,找到第一个不为 0 的,也就是通向 A 元素,那么接着再从竖列 A 元素开始向右,找到第一个不为 0 的,这里是 B 元素,但由于之前通向过 B 元素所以继续向右找不为 0 的,于此类推,最后得出 dfs 结果,如下代码

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

public class GraphByMatrix {

    private char[] arrayV;//顶点数组
    private int[][] matrix;//邻接矩阵
    private boolean isDirect;//是否是有向图

    /**
     * @param size 代表当前顶点的个数
     * @param isDirect
     */
    public GraphByMatrix(int size, boolean isDirect) {
        this.arrayV = new char[size];
        matrix = new int[size][size];
        //这里约定:统一初始化为最大值
        for(int i = 0; i < size; i++) {
            Arrays.fill(matrix[i], Constant.MAX);
        }
        this.isDirect = isDirect;
    }

    /**
     * 初始化顶点
     * @param array
     */
    public void initArrayV(char[] array) {
        for(int i = 0; i < array.length; i++) {
            arrayV[i] = array[i];
        }
    }

    /**
     * 增加边
     * @param srcV 起点
     * @param destV 终点
     * @param weight 权值
     */
    public void addEdge(char srcV, char destV, int weight) {
        int srcIndex = getIndexOfV(srcV);
        int destIndex = getIndexOfV(destV);

        matrix[srcIndex][destIndex] = weight;
        //如果是无向图,那么相反的位置,也同样需要置为空
        if(!isDirect) {
            matrix[destIndex][srcIndex] = weight;
        }
    }

    /**
     * 获取顶点的下标
     * @param v
     * @return
     */
    private int getIndexOfV(char v) {
        for(int i = 0; i < arrayV.length; i++) {
            if(arrayV[i] == v)
            return i;
        }
        return -1;
    }

    /**
     * 获取顶点的度,有向图 = 入度 + 出度
     * @return
     */
    public int getDevofV(char v) {
        int count = 0;
        int srcIndex = getIndexOfV(v);
        for(int i = 0; i < arrayV.length; i++) {
            if(matrix[srcIndex][i] != Constant.MAX) {
                count++;
            }
        }

        //计算有向图的入度
        if(isDirect) {
            for(int i = 0; i < arrayV.length; i++) {
                if(matrix[i][srcIndex] != Constant.MAX) {
                    count++;
                }
            }
        }
        return count;
    }

    //打印数组
    public void printGraph() {
        for(int i = 0; i < matrix.length; i++) {
            for(int j = 0; j < matrix[i].length; j++) {
                if(matrix[i][j] == Constant.MAX) {
                    System.out.print("∞ ");
                } else {
                    System.out.print(matrix[i][j] + " ");
                }
            }
            System.out.println();
        }
    }


    //广度优先遍历
    public void bfs(char v) {
        //数组用来标记顶点是否被使用过
        boolean[] visited = new boolean[arrayV.length];

        //定义一个队列,来辅助完成 bfs
        Queue<Integer> queue = new LinkedList<>();
        int srcIndex = getIndexOfV(v);
        queue.offer(srcIndex);
        while(!queue.isEmpty()) {
            int top = queue.poll();
            System.out.print(arrayV[top] + "->");
            visited[top] = true;//每次弹出一个元素就置为 true
            for(int i = 0; i < arrayV.length; i++) {
                if(matrix[top][i] != Constant.MAX && !visited[i]) {
                    queue.offer(i);
                    visited[i] = true;//每次入队就置为true
                }
            }
        }
    }

    // 深度优先遍历
    public void dfs(char v) {
        //数组用来标记顶点是否被使用过
        boolean[] visited = new boolean[arrayV.length];
        int srcIndex = getIndexOfV(v);
        dfsChild(srcIndex, visited);
    }

    private void dfsChild(int srcIndex, boolean[] visited) {
        System.out.print(arrayV[srcIndex] + "->");
        visited[srcIndex] = true;
        for(int i = 0; i < arrayV.length; i++) {
            if(matrix[srcIndex][i] != Constant.MAX && !visited[i]) {
                dfsChild(i, visited);
            }
        }
    }

    //测试
    public static void main(String[] args) {
        GraphByMatrix graph = new GraphByMatrix(4, false);
        char[] array = {'A', 'B', 'C', 'D'};
        graph.initArrayV(array);

        graph.addEdge('A', 'B', 1);
        graph.addEdge('A', 'D', 1);
        graph.addEdge('B', 'A', 1);
        graph.addEdge('B', 'C', 1);
        graph.addEdge('C', 'B', 1);
        graph.addEdge('C', 'D', 1);
        graph.addEdge('D', 'A', 1);
        graph.addEdge('D', 'C', 1);

//        graph.bfs('B');
        graph.dfs('B');
    }
}

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈亦康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值