图的表现形式第一篇(Java实现)

文章收藏的好句子:一直觉得当你足够努力,幸运总会和你不期而遇。

目录

1、图的表现形式

     1、1 图的基本介绍

     1、2 图的表现形式

     1、3 图的深度优先遍历

          1、3、1 图的深度优先遍历的思想

          1、3、2 图的深度优先遍历的算法步骤

1、图的表现形式

1、1 图的基本介绍

我们以前读大学的时候学过线性表和树,线性表局限于一个直接前驱和一个直接后继的关系,而树也只有一个直接前驱也就是父节点;当需要表示多对多关系的时候,树和线性表就不合适了,这时候就应该用到图了,好,我们现在列举一下图用的一些常用概念。

图是一种数据结构,其中结点可以具有零个或多个相邻元素,为了好理解,我先画一个图,如图1所示;

cc0fd28cbefffd5dd6f272403c822803.png

顶点:即是结点,比如图1中的A、B、C、D、E 。

边:2个顶点间连通的线,比如图1中的A到B的线 。

路径:从一个顶点到另一个顶点的所有路线,比如图1中的B->D的路径有B->D 和 B->A->D 这2条。

无项图:顶点之间的连接没有方向,比如图1的A->B 和 B->A 。

为了更好的理解有向图和带权图这2个概念,我分别在下面画出有向图和带权图,如图2和图3所示;

51d1083875d6848d83681c79dba9e767.png

b48ea0e6f08c61d9720894f0709e3a35.png

有向图:顶点之间的连接有方向,比如图2中的A->D,而不能D->A 。

带权图:顶点之间的直接连接连通带有权值,比如图3中的A和B的连通权值就是201 。

1、2 图的表现形式

图的表现形式有2种,一种是二维数组表示(邻接矩阵),另一种是用链表表示(邻接表)。

邻接矩阵(也就是二维数组嘛):是表示图形中顶点之间相邻关系的矩阵,对于N个顶点的图而言,矩阵的行和列分别表示N个点。

好,我们举个例子,将图转化成邻接矩阵,我们用图1转化为我们的邻接矩阵,其中邻接矩阵中的1表示图1中的2个顶点直接连通,0表示图1中的2个顶点不能直接连通,邻接矩阵如下所示;

d7c05a605856e7c231fcff780e0a7484.png

邻接矩阵需要为每个顶点分配 n 个边的空间,但是其实很多边在图中不存在,会造成额外的空间浪费,比如将图1转化的邻接矩阵,出现为0的二维数组元素,就造成额外的空间浪费。

为了方便理解邻接表,我们先画一张图,如图4所示;

214466863ea7decf426222bd5594f1c1.png

我们将图4的图转换成邻接表,如下所示;

e3c88d81bf712ab6f47fdf2da3cf28be.png

看图4转换的邻接表,它的实现只关心存在的边,不关心不存在的边,不存在空间浪费,邻接表由数组和链表组成(我们学过的Java中的HashMap就是数组和链表组成),看 A 这个顶点,与 A 直接相连接的顶点有B、E、D、C;邻接表下标为0的元素保存的链表是B->E->D->C 。

注意:邻接表里保存的顶点其实是A、B、C、D、E、F 直接相连的顶点,例,与 A 直接相连接的顶点有B、E、D、C,与 B 直接相连接的顶点有 A、E 。

1、3 图的深度优先遍历

1、3、1 图的深度优先遍历的思想

为了好方便理解图的深度优先遍历的思想,我们先画一张图,如图5所示;

31774ec2e5d63cda77484e0c5c4e50d1.png

(1)深度优先遍历,从初始访问节点出发,首先访问它的第一个邻接节点,然后以该邻接节点作为初始节点,继续访问它的第一个邻接节点,以此类推;我们就举个例子:看图4,假设我们以 A 节点作为初始节点,访问完 A 后,那么它的邻接节点就有B、E、D、C,然后我们就从B、E、D、C 里面拿出其中一个作为下一个初始节点。

(2)如果第一个邻接节点不存在或者已经递归遍历完成,则获取下一个邻接节点作为初始节点;举个例子,看图5,假设我们遍历顺序为 A->B->D->H->E,当遍历到E 的时候,发现 E 的邻接节点已经访问完了,就结束本次递归并回到 H 节点,H 的邻接节点也访问完了,就结束本次递归回到 D 节点,以此类推,直到结束完一次递归到 A 节点的时候,发现 A 的邻接节点 C 没有被访问过,那么将 C 作为下一个邻接节点。

(3)深度优先遍历是先向纵深挖掘遍历,纵深遍历完成后,再进行横向遍历;举个例子:图5的深度遍历的一种结果是 A->B->D->H->E->C->F->G,而不是像 A->B->C->D->E->F->G->H 这样一层一层的遍历。

(4)深度优先遍历时同时也是一个递归的过程;举个例子:看图5,假设我们遍历顺序为 A->B->D->H->E,当遍历到E 的时候,发现 E 的邻接节点已经访问完了,就结束本次递归回到 H 节点,H 的邻接节点也访问完了,就结束本次递归到 D 节点,以此类推,当发现有下一个邻接节点(未访问过的)时继续访问;当发现初始节点 A 的邻接节点已经访问完了时,算法结束。

1、3、2 图的深度优先遍历的算法步骤

ps:就拿图5的来举例,假设一开始 index = 0,从 A 顶点最开始访问

(1)访问初始节点 index,并将该节点标记为已访问;举例:假设初始节点为 index = 0,那么 A 顶点标记为已访问。

(2)查找初始节点 index 的第一个邻接节点 nextIndex;举例:查找初始节点 A 的第一个邻接节点 B。

(3)若 nextIndex 不存在,继续查找 index 的下一个邻接节点,又执行到(2)。

(4)如果 nextIndex 存在,又如果 nextIndex 未被访问,则对 nextIndex 进行深度优先遍历,将 index = nextIndex 又回到(1)步骤;举例:当执行遍历的过程中某一瞬间的顺序为A->B->D->H->E,当访问到E顶点的时候,E没有邻接顶点了,那么就结束递归回到H顶点的遍历,H顶点也没有邻接顶点了,也结束递归回到D顶点的遍历,D顶点也没有邻接顶点了,也结束递归回到B顶点的遍历,B顶点也没有邻接顶点了,也结束递归回到A顶点的遍历,发现A顶点有下一个邻接顶点C未被访问,所以 nextIndex 为C的索引,index = nextIndex 。

(5)若 nextIndex 已经被访问,继续查找 index 的下一个临界点,又执行到(3)步骤。

好了,我们用代码实现图5的深度优先遍历

(1)创建 MyChart 

package com.xiaoer.figure;


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


public class MyChart {


  /**
   * 创建图
   * @param vertexNum 顶点数量
   * @return
   */
  public static MyChart createChart(int vertexNum) {
    MyChart myChart = new MyChart(vertexNum);
    myChart.addVertices();
    myChart.addEdges();
    return myChart;
  }


  /**
   *  添加所有边,添加的是2个顶点直接相连的边,看图5,
   *  A与B是不是有一条边直接相连接,indexOf 方法返回的是传入的顶点
   *  在一维数组的位置(也就是下标)
   */
  private void addEdges() {
    addEdge(indexOf("A"), indexOf("B"), 1);
    addEdge(indexOf("A"), indexOf("C"), 1);
    addEdge(indexOf("B"), indexOf("D"), 1);
    addEdge(indexOf("B"), indexOf("E"), 1);
    addEdge(indexOf("C"), indexOf("F"), 1);
    addEdge(indexOf("C"), indexOf("G"), 1);
    addEdge(indexOf("D"), indexOf("H"), 1);
    addEdge(indexOf("E"), indexOf("H"), 1);
  }


  /**
   *  添加所有顶点,看图5,是不是添加了A-H 这8个顶点
   */
  private void addVertices() {
    addVertex("A");
    addVertex("B");
    addVertex("C");
    addVertex("D");
    addVertex("E");
    addVertex("F");
    addVertex("G");
    addVertex("H");
  }


  /**
   * 顶点数量
   */
  private int vertexNum;


  /**
   * 顶点列表,实际就是保存顶点的一维数组
   */
  private List<String> lstVertex;


  /**
   * 顶点路径
   */
  private int[][] vertexPathArray;


  /**
   * 边数量
   */
  private int edgeNum;


  /**
   * 是否已经被访问
   */
  private boolean[] isVisited;


  MyChart(int vertexNum) {
    this.vertexNum = vertexNum;
    lstVertex = new ArrayList<>(vertexNum);
    vertexPathArray = new int[vertexNum][vertexNum];
    isVisited = new boolean[vertexNum];
  }


  /**
   * 添加顶点,将顶点添加到一维数组
   * @param vertex 添加的顶点
   */
  void addVertex(String vertex) {
    if (vertexNum == lstVertex.size()) {
      throw new ArrayIndexOutOfBoundsException("数组已满");
    }
    lstVertex.add(vertex);
  }


  /**
   * 返回顶点所在的下标
   * @param vertex 目标顶点
   * @return 返回下标
   */
  int indexOf(String vertex) {
    return lstVertex.indexOf(vertex);
  }


  /**
   * 添加边,将边添加到二维数组(vertexPathArray)中
   * @param xIndex 横坐标
   * @param yIndex 纵坐标
   * @param weight 边的权重,weight为1时表示2个顶点直接相连接;
   * 
   * 看图5,边的二维数组(vertexPathArray)为:
   *   A B C D E F G H
   * A 0 1 1 0 0 0 0 0
   * B 1 0 0 1 1 0 0 0
   * C 1 0 0 0 0 1 1 0
   * D 0 1 0 0 0 0 0 1
   * E 0 1 0 0 0 0 0 1
   * F 0 0 1 0 0 0 0 0
   * G 0 0 1 0 0 0 0 0
   * H 0 0 0 1 1 0 0 0
   * 看到二维数组,所以vertexPathArray[xIndex][yIndex] = vertexPathArray[yIndex][xIndex]
   */
  void addEdge(int xIndex, int yIndex, int weight) {
    if (xIndex >= vertexNum || yIndex >= vertexNum) {
      throw new IndexOutOfBoundsException("索引越界");
    }
    vertexPathArray[xIndex][yIndex] = weight;
    vertexPathArray[yIndex][xIndex] = weight;
    edgeNum++;
  }


  /**
   * 获取边数量
   * @return 返回边数量
   */
  int getEdgeNum() {
    return edgeNum;
  }


  /**
   * 深度优先遍历 从第一个顶点开始进行遍历, 
   * 遍历过的顶点标记为已经遍历 先获取遍历该顶点的下一个邻接顶点 如果不存在,
   *  则继续第二个未遍历顶点开始
   * 如果存在, 判断该邻接顶点是否已经遍历过 如果没有遍历过, 
   * 则继续深度遍历该顶点(递归) 如果已经遍历过, 
   * 则继续寻找下一个邻接顶点
   */
  void dfs() {
    for (int i = 0; i < lstVertex.size(); i++) {
      // 从第一个节点开始进行遍历
      // 先访问初始节点
      if (!isVisited[i]) {
        dfs(i);
      }
    }
  }


  private void dfs(int index) {
    // 输出当前遍历的节点,
    System.out.print(lstVertex.get(index) + " -> ");


    // 标记当前节点已经访问
    isVisited[index] = true;


    // 获取它的第一个邻接节点进行访问
    int nextIndex = getFirstNeighbor(index);


    // 不等于-1说明存在下一个节点, 继续进行处理
    // 如果等于-1, 说明当前节点的邻接节点已经访问完
    while (nextIndex != -1) {


      // 如果没有被访问, 则继续深度循环遍历进行处理
      if (!isVisited[nextIndex]) {
        dfs(nextIndex);
      }


      // 如果已经被访问了, 则查找nextIndex的下一个邻接节点
      nextIndex = getNextNeighbor(index, nextIndex);
    }
  }
  


  /**
   * 已经访问完当前节点的第一个邻接节点,这里就获取当前节点的下一个邻接节点 
   * @param index 为 vertexPathArray 数组的行坐标
   * @param nextIndex 当前节点的邻接节点的纵坐标
   * @return 返回当前节点的下一个邻接节点 
   * 
   * 二维数组
   *   A B C D E F G H
   * A 0 1 1 0 0 0 0 0
   * B 1 0 0 1 1 0 0 0
   * C 1 0 0 0 0 1 1 0
   * D 0 1 0 0 0 0 0 1
   * E 0 1 0 0 0 0 0 1
   * F 0 0 1 0 0 0 0 0
   * G 0 0 1 0 0 0 0 0
   * H 0 0 0 1 1 0 0 0
   * 就拿A顶点这个了举例,假设访问完A顶点的第一个邻接顶点,第一个邻接节点的index = 0,
   * nextIndex = 1所以是不是从
   * vertexPathArray的第index行的第(nextIndex+1)列开始遍历到vertexPathArray的第一行的最后一列
   * 来寻找下一个邻接节点,
   * 所以 getNeighbor(index, nextIndex + 1)中的第二个参数为nextIndex + 1嘛,
   * 表示从第nextIndex + 1列开始遍历嘛
   */
  private int getNextNeighbor(int index, int nextIndex) {
    return getNeighbor(index, nextIndex + 1);
  }


  /**
   * 获取当前节点的第一个邻接节点 
   * @param index 为 vertexPathArray 数组的行坐标
   * @return 返回当前节点的第一个邻接节点 
   * 
   * 二维数组
   *   A B C D E F G H
   * A 0 1 1 0 0 0 0 0
   * B 1 0 0 1 1 0 0 0
   * C 1 0 0 0 0 1 1 0
   * D 0 1 0 0 0 0 0 1
   * E 0 1 0 0 0 0 0 1
   * F 0 0 1 0 0 0 0 0
   * G 0 0 1 0 0 0 0 0
   * H 0 0 0 1 1 0 0 0
   * 就拿A顶点这个了举例,要想得到A顶点的第一个邻接顶点,是不是从
   * vertexPathArray的第一行的第一列开始遍历到vertexPathArray的第一行的最后一列,
   * 所以 getNeighbor(index, 0)中的第二个参数为0嘛,表示从第一列开始遍历嘛
   */
  private int getFirstNeighbor(int index) {
    return getNeighbor(index, 0);
  }


  private int getNeighbor(int index, int nextIndex) {


    // 在该行坐标轴上进行遍历查找
    // 如果对应坐标的权值等于1, 证明这两个点是有直接关联关系的
    // 直接返回该点对应的纵坐标的下标索引
    for (int i = nextIndex; i < lstVertex.size(); i++) {
      if (vertexPathArray[index][i] == 1) {
        return i;
      }
    }


    // 如果没有找到, 直接返回-1
    return -1;
  }


}

(2)创建 FigureDemo 

package com.xiaoer.figure;


public class FigureDemo {
  public static void main(String[] args) {
        MyChart myChart = MyChart.createChart(8);
        System.out.println("深度优先遍历: ");
        myChart.dfs();
    }


}

程序运行如下所示:

3cd18a4d29816c2158600bb1d874caf5.png

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值