数据结构 - 图与深度优先、广度优先遍历

线性 -> 树 -> 图

为了解决问题,从线性结构 -> 树形结构 -> 图形结构

  1. 线性结构:只有前驱、后继结点,元素是线性关系

在这里插入图片描述

  1. 树形结构:元素有层次关系,每一层上的结点只能和上一层中的至多一个结点相关,但可能和下一层的多个结点相关

在这里插入图片描述

  1. 图形结构:元素之间具有任意关系,任意两个元素都可能相关

在这里插入图片描述


图由有限个顶点集合与顶点之间的边集合组成,用G(V,E)表示, 其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

在这里插入图片描述

有一些专业术语:

  • 无向边:顶点间没有方向的边,上图中的所有边都是无向边
  • 有向边:顶点间有方向的边
  • 子图:图G’(V’,E’)中V’是V的子集,E’是E的子集,G’是G的子图

在这里插入图片描述

  • 权:顶点或边上相关的数

在这里插入图片描述

  • 邻接点:对于无向图G= (V,E), 如果边(v,v’)属于E, 顶点v和v‘互为邻接点

上图【2,4】、【4,6】等互为邻接点

  • 度、入度和出度:点v的度是和v相关联的边的数目;有向图中,指向该结点的边数目是入度,由该结点出发的边数是出度

结点2的入度为1,出度为1,度为2

在这里插入图片描述

  • 路径和路径的长度:从顶点v 到顶点v’的路径是一个顶点序列,路径长度是经过的边数(有向图的路径也是有向的)

上图顶点4到5的路径:4->2->5,长度为2

  • 回路/环:起点等于终点的路径称为回路或环
  • 简单路径、简单回路或简单环:序列中顶点不重复出现的路径是简单路径
    简单回路即除起点/终点,其他顶点不重复的回路

图的存储结构

如同树的顺序存储、链式存储,图也有两种存储方式

  • 邻接矩阵:根据顶点数组生成矩阵

在这里插入图片描述

邻接矩阵优点:便于计算度和判断边
缺点:需要确定顶点数,不利于增删顶点;空间复杂度大,浪费了很多空间

  • 邻接表:数组+链表的形式,将每个顶点的邻接结点存入链表

在这里插入图片描述

邻接表优点:便于增删结点;便于统计边数;空间复杂度低
缺点:不便于判断结点是否连通、计算度

本文使用邻接矩阵


遍历方式

树有四种遍历方式:层次遍历、先序遍历、中序遍历、后序遍历
图有两种遍历方式:深度遍历、广度遍历

深度遍历

类似与树的先序遍历,对于一个图,遍历的顺序是:

  • 从起点开始,先访问第一个邻接结点,然后以该结点为起点,重复该步,直到起点没有未被访问的邻接点为止
  • 回到上一结点,继续访问下一邻接结点
  • 重复上一步,直到访问完毕

在这里插入图片描述

以4为起点,深度遍历:4->2->1->3->5->6,访问步骤

  1. 访问起点4
  2. 访问第一个邻接点2,以2为起点,访问第一个邻接点1
  3. 以1为起点,没有未被访问的邻接点,回到上一结点2,访问第二个连接点3
  4. 以3为起点,第一个邻接点为5;以5为起点,第一个邻接点为6;以6为起点,没有为被访问的邻接点,回到结点5,没有为被访问的邻接点,回到结点3,没有为被访问的邻接点,回到结点2,没有为被访问的邻接点,回到结点4,没有为被访问的邻接点
  5. 既然回到起点也没有为被访问的邻接点,遍历结束

通过递归可以实现

广度遍历

广度遍历有些类似与树的层次遍历:总是先遍历完一个顶点的所有邻接点再遍历下一个顶点

如:

在这里插入图片描述

遍历方法:

  1. 先访问起点4,得到4的所有邻接结点并存入队列
  2. 按顺序访问,每访问一个结点就往队列中存放它的所有邻接结点
  3. 访问完队列即广度遍历

广度优先遍历:4->2->6->1->3->5

广度优先遍历需要借助队列实现


Java实现

package com.company.graph;

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

/**
 * @author zfk
 *
 * 数据结构:图 -> 深度优先遍历、广度优先遍历
 */
public class Graph {
    //存储顶点集合
    private ArrayList<String> vertexList;
    //存储图对应的邻结矩阵
    private int[][] edges;
    //表示边的数目
    private int num;
    //定义一个数组,记录结点是否被访问
    private boolean[] isVisited;

    public static void main(String[] args) {
        //创建图
        int n = 6;
        String[] vertexValue = {"4","2","1","3","5","6"};

        Graph graph = new Graph(n);
        //添加顶点
        for (String value : vertexValue){
            graph.insertVertex(value);
        }
        //添加边 A-B,A-C,B-C,B-D,B-E
        graph.insertEdge(0,1,1);
        graph.insertEdge(0,5,1);
        graph.insertEdge(1,2,1);
        graph.insertEdge(1,3,1);
        graph.insertEdge(3,4,1);
        graph.insertEdge(3,5,1);
        graph.insertEdge(4,5,1);

        graph.showGraph();

        System.out.println("=== 深度遍历 ===");
        graph.dfs();
        System.out.println();
        System.out.println("=== 广度优先 ===");
        graph.bfs();
    }

    /**
     * @param n  图的顶点数
     */
    public Graph(int n){
        edges = new int[n][n];
        vertexList = new ArrayList<>(n);
        num = 0;
        isVisited = new boolean[n];
    }

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

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

    //返回结点个数
    public int getNumOfVertex(){
        return vertexList.size();
    }
    //返回边的数目
    public int getNum(){
        return num;
    }

    //返回结点i(下标)对应的数据 0->A 1->B
    public String getValueByIndex(int i){
        return vertexList.get(i);
    }


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

    //显示图对应的矩阵
    public void showGraph(){
        for (int[] i : edges){
            System.out.println(Arrays.toString(i));
        }
    }

    /**
     * @param index 查找的结点
     * @return 如果存在返回对应的下标
     * 得到当前查找结点的第一个邻结结点的下标,否则返回-1
     */
    private int getFirstNeighbor(int index){
        for (int j = 0;j < vertexList.size();j++){
            //下一个邻结点存在
            if (edges[index][j] > 0){
                return j;
            }
        }
        return -1;
    }

    //根据前一个邻结结点的下标获取下一个邻结结点
    private int getNextNeighbor(int v1,int v2){
        //循环二维数组中该结点的数组:B[1,0,1,1,1]
        for (int j = v2 + 1;j < vertexList.size();j++){
            if (edges[v1][j] > 0){
                return j;
            }
        }
        return -1;
    }

    /**
     * @param isVisited 判断该结点是否被访问
     * @param i 访问结点的下标,第一次是0
     * 深度优先遍历算法
     */
    private void dfs(boolean[] isVisited,int i){
        //访问结点输出
        System.out.print(getValueByIndex(i) + " - > ");
        //将该结点设置为已访问
        isVisited[i] = true;
        //找到结点i的邻接结点
        int w = getFirstNeighbor(i);
        //存在邻接结点
        while (w != -1){
            //w没有被访问
            if (isVisited[w] == false){
               dfs(isVisited,w);
            }
            //如果w被访问,查找当前结点的下一个结点
            w = getNextNeighbor(i,w);
        }
    }
    //重载dfs,遍历所有节点
    public void dfs(){
        for (int i = 0;i < getNumOfVertex();i++){
            if (!isVisited[i]){
                dfs(isVisited,i);
            }
        }
    }


    /**
     * @param isVisited 结点是否被访问的数组
     * @param i 当前结点对应的下标
     * 广度优先
     */
    private void bfs(boolean[] isVisited,int i){
        //表示队列的头结点对应的下标
        int u;
        //邻接结点w
        int w;
        //队列,记录结点访问的顺序
        LinkedList linkedList = new LinkedList();
        //访问结点,输出结点
        System.out.print(getValueByIndex(i)+" - > ");
        //标记当前结点为已访问
        isVisited[i] = true;
        //将结点加入队列
        linkedList.addLast(i);

        //当队列不为空,遍历队列
        while (!linkedList.isEmpty()){
            //取出队列头结点
            u = (int) linkedList.removeFirst();
            //得到该结点的第一个邻接结点的下标
            w = getFirstNeighbor(u);

            //当找到邻接结点
            while (w != -1){
                //判断邻接结点是否访问过
                if (!isVisited[w]){
                    //输出邻接结点
                    System.out.print(getValueByIndex(w)+" - > ");
                    //标记为已访问
                    isVisited[w] = true;
                    //加入队列
                    linkedList.addLast(w);
                }
                //以u为前驱结点,找到w后的下一个邻接结点
                w = getNextNeighbor(u,w);

            }
        }
    }

    //重载广度优先
    public void bfs(){
        //初始化
        isVisited = new boolean[vertexList.size()];
        //遍历初始结点对应的行
        for (int i = 0;i < getNumOfVertex();i++){
            if (!isVisited[i]){
                bfs(isVisited,i);
            }
        }
    }
}

遍历结果:

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值