博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

图的遍历

Posted on 2020-08-20 17:29  池塘鱼  阅读(150)  评论(0)    收藏  举报

准备工作

图的结构:

  //结点集合
    ArrayList<String> nodes;
    //边的个数
    private int edgeNum;
    //邻接矩阵:边是否连通或边之间的权值
    private int[][] weight;
    //记录结点是否被访问过:用于遍历
    private boolean[] visited;

构造器:

  //构造器
    public Graph(int n) {
        this.nodes = new ArrayList<>(n);
        this.edgeNum = 0;
        this.weight = new int[n][n];
    }

给图添加结点和边:这里默认是无向图

   //添加结点
    public void insertNode(String node){
        nodes.add(node);
    }

    //添加边
    public void insertEdge(int v1,int v2,int w){
        weight[v1][v2]=w;
        weight[v2][v1]=w;
        edgeNum++;
    }

显示图对应的邻接矩阵:

 

  //显示图对应的邻接矩阵
    public void showGraph(){
        for (String val:nodes) {
            System.out.println(val);
        }
    }

 

理解遍历执行过程

开始遍历算法代码

  1.深度遍历优先

  • 特点:深度遍历优先是纵向深挖的遍历,是一种顺着一个结点得到其邻接结点后不断挖掘邻接结点的邻接结点的算法。
  • 思路:从图中某个顶点V0出发,访问该结点,然后从未被访问过的V0的邻接结点出发,继续深度遍历图中其余结点,直至所有与V0连通的结点都被遍历过。然后再重新选择一个结点出发,重复上述过程,直至所有结点都被遍历过。
  • 代码:
     1 //深度优先搜索
     2     public void dfs(){
     3         int size = nodes.size();
     4         visited=new boolean[size];
     5         //便于遍历完一个结点的所有邻接结点后,重新选择结点
     6         for (int i = 0; i < size; i++) {
     7             if (!visited[i]){
     8                 dfs(i);
     9             }
    10         }
    11     }
    12 
    13     /**
    14      * 获取当前结点的所有连通结点
    15      * @param i 当前结点的下标
    16      */
    17     private void dfs(int i) {
    18         //从当前结点出发
    19         System.out.print(nodes.get(i)+" ");
    20         visited[i]=true;
    21         //获取当前结点的邻接结点
    22         int neighbor = getFirstNeighbor(i);
    23         //下面的这个循环是核心所在
    24         //如果邻接结点未被访问过,则继续访问邻接结点的邻接结点;否则,回溯到上一层,重新选择邻接结点。直到遍历完所有与当前结点连通的结点。
    25         while (neighbor!=-1){
    26             if (!visited[neighbor]){
    27                 dfs(neighbor);
    28             }else//在这里实现当前结点的第二、三...个直接邻接结点进行深度遍历 
    29                 neighbor=getNextNeighbor(i,neighbor);
    30             }
    31         }
    32 
    33     }
    34 
    35     /**
    36      * 获得当前结点的第一个邻接结点
    37      * @param index 当前结点对应的下标
    38      * @return 如果当前结点有邻接结点,则返回第一个找到的邻接结点的下标;否则,返回-1.
    39      */
    40     private int getFirstNeighbor(int index) {
    41         for (int j = 0; j < nodes.size(); j++) {
    42             if (weight[index][j]>0){
    43                 return j;
    44             }
    45         }
    46         return -1;
    47     }
    48 
    49     /**
    50      *
    51      * @param v1 当前结点
    52      * @param v2 当前结点已经访问过的相邻接点
    53      * @return 返回剩下的当前结点尚未访问过的相邻接点中的第一个的下标;如果当前结点没有尚未访问过的相邻接点,则返回-1。
    54      */
    55     public int getNextNeighbor(int v1,int v2) {
    56         for (int i = v2+1; i < nodes.size(); i++) {
    57             if (weight[v1][i]>0){
    58                 return i;
    59             }
    60         }
    61         return -1;
    62     }

     

  • 重新整理思路:
    1. 选择一个未被访问过的结点,出发;
    2. 访问当前结点,然后访问当前结点的第一个邻接结点,并深度遍历该结点的所有邻接结点,然后访问当前结点的第二个邻接结点并深度遍历该结点的所有结点...直至当前结点的所有邻接结点都被访问过。此时,与当前结点连通的所有结点都被访问完;
    3. 重复1,2步,直至所有结点都被访问过。

  2.广度优先搜索

  • 特点:广度遍历优先是横向的遍历,是一种类似于树的层级遍历的算法。
  • 思路:从图中的某个结点V0出发,访问该结点的邻接结点,然后按照访问邻接结点的顺序依次访问这些邻接结点的邻接结点,直至图中所有和V0连通的结点都被访问到。然后重新选取一个未被访问过的结点重复上述过程。
  • 代码:
     1   //广度优先搜索
     2     public void bfs(){
     3         int size = nodes.size();
     4         visited=new boolean[size];
     5         //便于访问完一个连通子图后,可以重新选择出发结点
     6         for (int i = 0; i < nodes.size(); i++) {
     7             if (!visited[i]){
     8                 bfs(i);
     9             }
    10         }
    11     }
    12
    13 /** 14 * 获取当前结点的所有连通结点(广度遍历) 15 * @param i 出发结点(当前结点)的下标 16 */ 17 private void bfs(int i) { 18 //准备 19 LinkedList<Integer> queue=new LinkedList<>(); 20 //访问当前结点 21 System.out.print(nodes.get(i)+" "); 22 visited[i]=true; 23 queue.add(i); 24 //遍历当前结点的所有连通结点 25 while (!queue.isEmpty()){ 26 //从队列中取出一个结点,开始某个结点的新的一层的遍历 27 int curr = queue.removeFirst(); 28 //遍历一层:获取取出结点的所有直接相邻结点 29 int neighbor = getFirstNeighbor(curr); 30 while (neighbor!=-1){ 31 if (!visited[neighbor]) { 32 System.out.print(nodes.get(neighbor)+" "); 33 visited[neighbor]=true; 34 queue.add(i); 35 } 36 neighbor=getNextNeighbor(curr,neighbor); 37 } 38 } 39 } 40
    41   /** 42 * 获得当前结点的第一个邻接结点 43 * @param index 当前结点对应的下标 44 * @return 如果当前结点有邻接结点,则返回第一个找到的邻接结点的下标;否则,返回-1. 45 */ 46 private int getFirstNeighbor(int index) { 47 for (int j = 0; j < nodes.size(); j++) { 48 if (weight[index][j]>0){ 49 return j; 50 } 51 } 52 return -1; 53 } 54
    55 /** 56 * 57 * @param v1 当前结点 58 * @param v2 当前结点已经访问过的相邻接点 59 * @return 返回剩下的当前结点尚未访问过的相邻接点中的第一个的下标;如果当前结点没有尚未访问过的相邻接点,则返回-1。 60 */ 61 public int getNextNeighbor(int v1,int v2) { 62 for (int i = v2+1; i < nodes.size(); i++) { 63 if (weight[v1][i]>0){ 64 return i; 65 } 66 } 67 return -1; 68 }

     

  • 重新整理思路:
    1. 选择一个未被访问过的结点,出发;
    2. 访问当前结点,然后访问当前结点的第一个邻接结点,第二个邻接结点...直至访问完当前结点的所有直接邻接结点。此时,当前结点的直接子层结点访问完毕;
    3. 依次选择上一层访问过的结点,访问其直接相邻结点,直至访问完所有的上一层结点的直接相邻结点。此时,访问完当前结点的第二层结点;
    4. 重复3,访问完当前结点的第三层结点...直至访问完当前结点的所有连通结点;
    5. 重新选择有一个未被访问过的结点出发,重复1,2,3,4步来完成一个结点的子图的广度遍历。