图(graph)

图基本介绍

  • 前面我们学了线性表和树
  • 线性表局限于一个直接前驱和一个直接后续的关系
  • 树也只能由一个直接前驱也就是父节点
  • 当我们需要表示多对多的关系时,就需要用到图
    在这里插入图片描述
    图是一种数据结构,其中节点可以具有零个或者多个相邻元素。两个结点之间连接称为边。结点也可以称为顶点
    在这里插入图片描述
    有向图:顶点之间的连接有方向,比如A->B,只能A->B,不能是B->A
    带权图:这种边带权值的图也叫网。

图的表示方式

图的表示方式有两种:二维数组表示(邻接矩阵);链表表示(邻接表)

邻接矩阵
邻接矩阵是表示图形中顶点之间相邻关系的矩阵,对于n个顶点的图而言,矩阵是row和col表示的是1…n个点。
在这里插入图片描述

邻接表

  • 邻接矩阵需要为每个顶点都分配n个边的空间,其实很多边都是不存在,会造成空间的一定损失
  • 邻接表的实现只关心存在的边,不关心不存在的边。因此没有空间浪费,邻接表由数组+链表组成
    在这里插入图片描述
    说明:
  • 标号为0的节点的相关联的结点为1 2 3 4
  • 标号为1的节点的相关联节点为 0 4
  • 标号为2 的可带你相关联的节点为 0 4 5

入门案例

要求:代码实现如下图结构
在这里插入图片描述
思路

  • 存储顶点String使用ArrayList
  • 保存矩阵int[][] edges

代码实现

public class Graph {
    //存储节点的集合
    private ArrayList<String> vertexList;
    //存储对应邻接矩阵
    private int[][] edges;
    //表示边的数目
    private int edgeNum;

    public Graph(int num) {
        vertexList = new ArrayList<>(num);
        edges = new int[num][num];
        edgeNum =0;
    }

    public static void main(String[] args) {
        int num = 5;
        String[] vertexArr = {"A","B","C","D","E"};
        Graph graph = new Graph(5);

        for (String vertex:vertexArr) {
            graph.insertVertex(vertex);
        }
        // A-B A-C  B-C  B-D   E-B
        graph.insertEdge(0,1,1);//A-B
        graph.insertEdge(0,2,1);//A-C
        graph.insertEdge(1,2,1);//B-C
        graph.insertEdge(1,3,1);//B-D
        graph.insertEdge(1,4,1);//E-B

        graph.showGraph();
    }

    //图常用的方法
    //1 返回节点个数
    public int getNumberOfVertex(){
        return vertexList.size();
    }

    //2 返回边的数目
    public int getEdgeNum(){
        return edgeNum;
    }

    //3 返回节点i(下标)对应的顶点
    public String getValueByIndex(int i){
        return vertexList.get(i);
    }

    //4 返回 v1 v2 对应的权值
    public int getWeight(int v1,int v2){
        return edges[v1][v2];
    }

    //遍历
    private void showGraph() {
        for (int[] vertexArr:edges) {
            System.out.println(Arrays.toString(vertexArr));
        }
    }

    //插入节点
    public void insertVertex(String vertex){
        vertexList.add(vertex);
    }

    /**
     * 添加边
     * @param v1 第一个顶点下标
     * @param v2 第二个顶点下标
     * @param weight 边的权值
     */
    public void insertEdge(int v1,int v2,int weight){
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        edgeNum++;
    }
}

图的深度优先遍历

图遍历介绍
所谓图的遍历 即是对节点的访问。一个图有那么多个节点,如何遍历这些节点,需要特点的策略,一般有两种访问策略
1深度优先遍历
2广度优先遍历

深度优先遍历基本思想
图的深度优先搜索(Depth First Search)

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

深度优先遍历算法步骤
1.访问初始节点v,并标记节点v为已访问
2.查找节点v的第一个邻节点W
3.若w存在,则继续执行4,如果W不存在,则回到第一步,将从v的下一个节点继续。
4.若W未被访问,对w进行深度优先遍历递归(即把w当做另一个v,然后进行步骤123)
5.查找节点v的w邻节点的下一个邻节点,转到步骤3

代码实现

  //标记顶点是否访问
    private boolean[] isVisited;

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

    private void dfs(boolean[] isVisited, int i) {
        //首先 访问该节点
        System.out.print(getValueByIndex(i)+ "->");
        // 将该节点设置已访问
        isVisited[i] = true;
        // 查找下标为i的相邻节点下标
        int first = getFirstNeighborIndex(i);
        if(first!=-1){
            if(!isVisited[first]){//没有被访问过
                dfs(isVisited,first);
            }
            //如果下一节点被访问过了 去找下一个的下一个相邻节点
            first =  getSecondNeighborIndex(i,first);
        }
    }

    /**
     * 根据当前节点及下一节点 获取下一个相邻节点
     * @param i
     * @param first
     * @return
     */
    private int getSecondNeighborIndex(int i, int first) {
        for (int j = first+1; j <vertexList.size(); j++) {
            if(edges[i][j]>0){
                return j;
            }
        }
        return -1;
    }

    /**
     *  查找下标为i的相邻节点下标
     * @param index
     * @return
     */
    private int getFirstNeighborIndex(int index) {
        for (int j = 0; j <vertexList.size(); j++) {
            if(edges[index][j]>0){
                return j;
            }
        }
        return -1;
    }



//测试代码
System.out.println("深度优先遍历");
graph.dfs(num); // A->B->C->D->E->

图的广度优先遍历

1.访问初始节点v,并标记节点v为已访问
2.节点v入队列
3.当队列非空时,继续执行,否则算法结束
4.出队列,取得对头节点u
5.查找节点u的第一个邻节点w
6.若节点u的邻节点w不存在,则转到步骤3;否则循环执行以下三个步骤:

  • 若节点w尚未被访问,则访问节点w并标记为已访问
  • 节点w入队列
  • 查找节点u的继续w邻节点后的下一个邻节点w转到步骤6

代码实现

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

    private void bfs(boolean[] isVisited, int i) {
        //表示队列头节点的下标
        int headIndex;
        //表示邻第一个节点的下标
        int first;

        //队列 存放访问过的节点(先进先出)
        LinkedList<Integer> queue = new LinkedList<>();
        //访问当前节点
        System.out.print(getValueByIndex(i)+ "->");
        //标记为已访问
        isVisited[i] = true;
        //将节点加入队列
        queue.addLast(i);
        while(!queue.isEmpty()){
            //取出队列的头节点坐标
            headIndex = queue.removeFirst();
            first = getFirstNeighborIndex(headIndex);
            while(first!=-1){
                if(!isVisited[first]){
                    System.out.print(getValueByIndex(first)+ "->");
                    isVisited[first] = true;
                    //入队列
                    queue.addLast(first);
                }
                //已经放过的话已 headIndex 为前驱找nextIndex的下一个邻节点
                //提现广度优先
                first = getSecondNeighborIndex(headIndex,first);
            }
        }
    }


//测试代码
 System.out.println();
 System.out.println("广度优先遍历");
 graph.bfs(num); // A->B->C->D->E->

整体代码

public class Graph {
    //存储节点的集合
    private ArrayList<String> vertexList;
    //存储对应邻接矩阵
    private int[][] edges;
    //表示边的数目
    private int edgeNum;

    public Graph(int num) {
        vertexList = new ArrayList<>(num);
        edges = new int[num][num];
        edgeNum =0;
    }

    public static void main(String[] args) {
        int num = 5;
        String[] vertexArr = {"A","B","C","D","E"};
        Graph graph = new Graph(5);

        for (String vertex:vertexArr) {
            graph.insertVertex(vertex);
        }
        // A-B A-C  B-C  B-D   E-B
        graph.insertEdge(0,1,1);//A-B
        graph.insertEdge(0,2,1);//A-C
        graph.insertEdge(1,2,1);//B-C
        graph.insertEdge(1,3,1);//B-D
        graph.insertEdge(1,4,1);//E-B

        graph.showGraph();

        System.out.println("深度优先遍历");
        graph.dfs(num); // A->B->C->D->E->

        System.out.println();
        System.out.println("广度优先遍历");
        graph.bfs(num); // A->B->C->D->E->

    }

    //图常用的方法
    //1 返回节点个数
    public int getNumberOfVertex(){
        return vertexList.size();
    }

    //2 返回边的数目
    public int getEdgeNum(){
        return edgeNum;
    }

    //3 返回节点i(下标)对应的顶点
    public String getValueByIndex(int i){
        return vertexList.get(i);
    }

    //4 返回 v1 v2 对应的权值
    public int getWeight(int v1,int v2){
        return edges[v1][v2];
    }

    //遍历
    private void showGraph() {
        for (int[] vertexArr:edges) {
            System.out.println(Arrays.toString(vertexArr));
        }
    }

    //插入节点
    public void insertVertex(String vertex){
        vertexList.add(vertex);
    }

    /**
     * 添加边
     * @param v1 第一个顶点下标
     * @param v2 第二个顶点下标
     * @param weight 边的权值
     */
    public void insertEdge(int v1,int v2,int weight){
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        edgeNum++;
    }


    //标记顶点是否访问
    private boolean[] isVisited;

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

    private void dfs(boolean[] isVisited, int i) {
        //首先 访问该节点
        System.out.print(getValueByIndex(i)+ "->");
        // 将该节点设置已访问
        isVisited[i] = true;
        // 查找下标为i的相邻节点下标
        int first = getFirstNeighborIndex(i);
        if(first!=-1){
            if(!isVisited[first]){//没有被访问过
                dfs(isVisited,first);
            }
            //如果下一节点被访问过了 去找下一个的下一个相邻节点
            first =  getSecondNeighborIndex(i,first);
        }
    }

    /**
     * 根据当前节点及下一节点 获取下一个相邻节点
     * @param i
     * @param first
     * @return
     */
    private int getSecondNeighborIndex(int i, int first) {
        for (int j = first+1; j <vertexList.size(); j++) {
            if(edges[i][j]>0){
                return j;
            }
        }
        return -1;
    }

    /**
     *  查找下标为i的相邻节点下标
     * @param index
     * @return
     */
    private int getFirstNeighborIndex(int index) {
        for (int j = 0; j <vertexList.size(); j++) {
            if(edges[index][j]>0){
                return j;
            }
        }
        return -1;
    }


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

    private void bfs(boolean[] isVisited, int i) {
        //表示队列头节点的下标
        int headIndex;
        //表示邻第一个节点的下标
        int first;

        //队列 存放访问过的节点(先进先出)
        LinkedList<Integer> queue = new LinkedList<>();
        //访问当前节点
        System.out.print(getValueByIndex(i)+ "->");
        //标记为已访问
        isVisited[i] = true;
        //将节点加入队列
        queue.addLast(i);
        while(!queue.isEmpty()){
            //取出队列的头节点坐标
            headIndex = queue.removeFirst();
            first = getFirstNeighborIndex(headIndex);
            while(first!=-1){
                if(!isVisited[first]){
                    System.out.print(getValueByIndex(first)+ "->");
                    isVisited[first] = true;
                    //入队列
                    queue.addLast(first);
                }
                //已经放过的话已 headIndex 为前驱找nextIndex的下一个邻节点
                //提现广度优先
                first = getSecondNeighborIndex(headIndex,first);
            }
        }
    }
}

打印信息
在这里插入图片描述

图的深度优先VS广度优先

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值