图(Java)

  • Post author:
  • Post category:java




图的基本介绍



图的引入


线性表和树存在的不足:

  • 线性表局限于一个直接前驱和一个直接后继的关系。
  • 树也只能有一个直接前驱也就是父节点。


这两种数据结构都无法表示多对多的关系,所以我们需要引入图的概念



图的举例说明

图是一种

数据结构

,其中结点可以具有零个或多个相邻元素。两个结点之间的连接称为边。结点也可以称为顶点。如图:

图的举例



图的常用概念

  • 顶点(vertex):即组成图的各个节点。
  • 边(edge):两个相关联的顶点的连线
  • 路径:从u到v的一条路径是指一个序列v0,e1,v1,e2,v2,…ek,vk,其中ei的顶点为vi及vi – 1,k称作路径的长度。如果它的起止顶点相同,该路径是“闭”的,反之,则称为“开”的。一条路径称为一简单路径(simple path),如果路径中除起始与终止顶点可以重合外,所有顶点两两不等。
  • 无向图:如果图的每条边无确定方向,那么得到的图称为无向图
  • 有向图:如果给图的每条边规定一个方向,那么得到的图称为有向图
  • 带权图:边带有权重的图。


以下为几张图片实例:


示例1

示例二



图的表示方式



邻接矩阵

邻接矩阵是表示图形中顶点之间相邻关系的矩阵,如下图所示:

邻接矩阵



邻接表

  • 邻接矩阵需要为每个顶点都分配n个边的空间,其实有很多边都是不存在,会造成空间的一定损失。
  • 邻接表的实现只关心存在的边,不关心不存在的边。因此没有空间浪费,邻接表由数组+链表组成。

如下图:

邻接表



图的构建

要求:代码实现下面的图结构(以邻接数组的形式实现):

图

在这里插入图片描述


代码实现:

package com.atschool.graph;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * Description:
 * Author:江洋大盗
 * Date:2021/1/16 19:42
 */
public class Graph {
    private List<String> vertexList;//存储顶点集合
    private int[][] edges;//存储图对应的邻接矩阵
    private int numOfEdge;//记录边的个数

    public Graph(int n) {
        //初始化矩阵和vertexList
        edges = new int[n][n];
        vertexList = new ArrayList<>(n);
        numOfEdge = 0;
    }

    public static void main(String[] args) {
        //创建图对象
        Graph graph = new Graph(5);
        //循环的添加节点
        String[] vertexes = {"A", "B", "C", "D", "E"};
        for (String vertex : vertexes) {
            graph.insertVertex(vertex);
        }
        //添加边
        graph.addEdge(0, 1, 1);
        graph.addEdge(0, 2, 1);
        graph.addEdge(1, 2, 1);
        graph.addEdge(1, 3, 1);
        graph.addEdge(1, 4, 1);
        //显示邻接矩阵
        graph.showGraph();
    }
    // *******以下为图中的常用方法********
    
    /**
     * 添加节点
     *
     * @param vertex 待插入的节点
     */
    public void insertVertex(String vertex) {
        vertexList.add(vertex);
    }

    /**
     * 显示邻接矩阵
     */
    public void showGraph() {
        for (int[] link : edges) {
            System.out.println(Arrays.toString(link));
        }
    }
    
    /**
     * @param v1     第一个值的下标
     * @param v2     第二个值的下标
     * @param weight 两个值的权重
     */
    public void addEdge(int v1, int v2, int weight) {
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        numOfEdge++;
    }
}



图的深度优先遍历



深度优先遍历基本思想

图的深度优先搜索(Depth First Search);

  • 深度优先遍历,从初始访问结点出发,初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接结点,可以这样理解:每次都在访问完当前结点后首先访问当前结点的第一个邻接结点。
  • 我们可以看到,这样的访问策略是优先往纵向挖掘深入,而不是对一个结点的所有邻接结点进行横向访问。
  • 显然,深度优先搜索是一个递归的过程。



代码实现深度优先遍历

/**
     * 对i节点进行深度优先遍历
     *
     * @param isVisited 记录节点是否被访问的数组
     * @param i         起始节点
     */
    private void dfs(boolean[] isVisited, int i) {
        //1.首先输出该节点
        System.out.print(getVertexByIndex(i) + "->");
        //2.将该节点设置为已经访问过
        isVisited[i] = true;
        //3.查找节点i的第一个邻接节点w
        int w = getFirstNeighbor(i);
        while (w != -1) {//说明下一个节点存在
            if (!isVisited[w]) {//说明该节点没有被访问
                dfs(isVisited, w);
            }
            //如果w节点已经被访问
            w = getNextNeighbor(i, w);
        }
    }

    /**
     * 深度优先遍历
     */
    public void dfs() {
        isVisited = new boolean[vertexList.size()];
        for (int i = 0; i < vertexList.size(); i++) {
            if (!isVisited[i])
                dfs(isVisited, i);
        }
    }



图的广度优先遍历



广度优先遍历基本思想

图的广度优先搜索(Broad First Search),类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的结点的顺序,以便按这个顺序来访问这些结点的邻接结点。



代码实现广度优先遍历

/**
     * 对一个结点进行广度优先遍历的方法
     *
     * @param isVisited 记录节点是否被访问的数组
     * @param i         起始节点
     */
    private void bfs(boolean[] isVisited, int i) {
        int u;//表示队列的头节点对应的下标
        int w;//邻接节点w
        //队列记录节点的访问顺序
        LinkedList<Integer> queue = new LinkedList<>();
        //访问节点,输出节点信息
        System.out.print(getVertexByIndex(i) + "->");
        //标记该节点为已经访问
        isVisited[i] = true;
        //将该节点加入队列
        queue.addLast(i);
        while (!queue.isEmpty()) {
            //取出队列的头节点下标
            u = queue.removeFirst();
            //得到第一个邻接节点的下标w
            w = getFirstNeighbor(u);
            while (w != -1) {
                //说明w存在
                if (!isVisited[w]) {
                    //说明没有访问过
                    System.out.print(getVertexByIndex(w) + "->");
                    //标记为访问过
                    isVisited[w] = true;
                    //加入队列
                    queue.addLast(w);
                }
                //找w后面的下一个邻结点
                w = getNextNeighbor(u, w);
            }
        }
    }

    /**
     * 广度优先遍历
     */
    public void bfs() {
        isVisited = new boolean[vertexList.size()];
        for (int i = 0; i < vertexList.size(); i++) {
            if (!isVisited[i]) {
                bfs(isVisited, i);
            }
        }
    }



图的代码汇总

package com.atschool.graph;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * Description:
 * Author:江洋大盗
 * Date:2021/1/16 19:42
 */
public class Graph {
    private List<String> vertexList;//存储顶点集合
    private int[][] edges;//存储图对应的邻接矩阵
    private int numOfEdge;//记录边的个数
    private boolean[] isVisited;//记录某个顶点是否被访问

    public Graph(int n) {
        //初始化矩阵和vertexList
        edges = new int[n][n];
        vertexList = new ArrayList<>(n);
        numOfEdge = 0;
    }

    public static void main(String[] args) {
        //创建图对象
        Graph graph = new Graph(5);
        //循环的添加节点
        String[] vertexes = {"A", "B", "C", "D", "E"};
        for (String vertex : vertexes) {
            graph.insertVertex(vertex);
        }
        //添加边
        graph.addEdge(0, 1, 1);
        graph.addEdge(0, 2, 1);
        graph.addEdge(1, 2, 1);
        graph.addEdge(1, 3, 1);
        graph.addEdge(1, 4, 1);
        //显示邻接矩阵
        graph.showGraph();
        System.out.println("深度优先遍历");
        graph.dfs();
        System.out.println("\n广度优先遍历");
        graph.bfs();

    }

    /**
     * 对i节点进行深度优先遍历
     *
     * @param isVisited 记录节点是否被访问的数组
     * @param i         起始节点
     */
    private void dfs(boolean[] isVisited, int i) {
        //1.首先输出该节点
        System.out.print(getVertexByIndex(i) + "->");
        //2.将该节点设置为已经访问过
        isVisited[i] = true;
        //3.查找节点i的第一个邻接节点w
        int w = getFirstNeighbor(i);
        while (w != -1) {//说明下一个节点存在
            if (!isVisited[w]) {//说明该节点没有被访问
                dfs(isVisited, w);
            }
            //如果w节点已经被访问
            w = getNextNeighbor(i, w);
        }
    }

    /**
     * 深度优先遍历
     */
    public void dfs() {
        isVisited = new boolean[vertexList.size()];
        for (int i = 0; i < vertexList.size(); i++) {
            if (!isVisited[i])
                dfs(isVisited, i);
        }
    }

    /**
     * 对一个结点进行广度优先遍历的方法
     *
     * @param isVisited 记录节点是否被访问的数组
     * @param i         起始节点
     */
    private void bfs(boolean[] isVisited, int i) {
        int u;//表示队列的头节点对应的下标
        int w;//邻接节点w
        //队列记录节点的访问顺序
        LinkedList<Integer> queue = new LinkedList<>();
        //访问节点,输出节点信息
        System.out.print(getVertexByIndex(i) + "->");
        //标记该节点为已经访问
        isVisited[i] = true;
        //将该节点加入队列
        queue.addLast(i);
        while (!queue.isEmpty()) {
            //取出队列的头节点下标
            u = queue.removeFirst();
            //得到第一个邻接节点的下标w
            w = getFirstNeighbor(u);
            while (w != -1) {
                //说明w存在
                if (!isVisited[w]) {
                    //说明没有访问过
                    System.out.print(getVertexByIndex(w) + "->");
                    //标记为访问过
                    isVisited[w] = true;
                    //加入队列
                    queue.addLast(w);
                }
                //找w后面的下一个邻结点
                w = getNextNeighbor(u, w);
            }
        }
    }

    /**
     * 广度优先遍历
     */
    public void bfs() {
        isVisited = new boolean[vertexList.size()];
        for (int i = 0; i < vertexList.size(); i++) {
            if (!isVisited[i]) {
                bfs(isVisited, i);
            }
        }
    }

    //********两个得到邻接节点的方法******

    /**
     * @param index 当前节点的下标
     * @return 如果存在就返回下一个节点的下标,否则返回-1.
     */
    public int getFirstNeighbor(int index) {
        for (int i = 0; i < vertexList.size(); i++) {
            if (edges[index][i] > 0) {
                return i;
            }
        }
        return -1;
    }

    /**
     * @param v1 第一个节点的下标
     * @param v2 第二个节点的下标(此时,此下标已经被访问过)
     * @return 根据前一个邻接结点的下标来获取下一个邻接结点, 找不到返回-1
     */
    public int getNextNeighbor(int v1, int v2) {
        for (int i = v2 + 1; i < vertexList.size(); i++) {
            if (edges[v1][i] > 0) {
                return i;
            }
        }
        return -1;
    }

    // *******以下为图中的常用方法********


    /**
     * @return 返回顶点的个数
     */
    public int getNumOfVertex() {
        return vertexList.size();
    }

    /**
     * @return 返回边的个数
     */
    public int getNumOfEdge() {
        return numOfEdge;
    }

    /**
     * 显示邻接矩阵
     */
    public void showGraph() {
        for (int[] link : edges) {
            System.out.println(Arrays.toString(link));
        }
    }

    /**
     * @param index 要得到顶点的下标
     * @return 返回要找的顶点
     */
    public String getVertexByIndex(int index) {
        return vertexList.get(index);
    }

    /**
     * @param v1 第一个值的下标
     * @param v2 第二个值的下标
     * @return 返回两个值的权重
     */
    public int getWeight(int v1, int v2) {
        return edges[v1][v2];
    }

    /**
     * 添加节点
     *
     * @param vertex 待插入的节点
     */
    public void insertVertex(String vertex) {
        vertexList.add(vertex);
    }

    /**
     * @param v1     第一个值的下标
     * @param v2     第二个值的下标
     * @param weight 两个值的权重
     */
    public void addEdge(int v1, int v2, int weight) {
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        numOfEdge++;
    }
}


测试结果

(注:深度优先遍历与广度优先遍历结果不一定相同,本例中因为图的形式较为简单,所以结果相同):

测试结果




结语


只要能收获甜蜜,荆棘丛中也有蜜蜂忙碌的身影,未来的你一定会感谢现在努力的自己。



版权声明:本文为m0_52099927原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。