算法和数据结构

图的遍历 --- 深度优先遍历

2020-12-24  本文已影响0人  贪挽懒月

在讲深度优先遍历之前,先来回顾一下图这种数据结构。

1. 是什么?

图,也是一种数据结构,其节点可以具有零个或者多个相邻元素,两个节点之间的连接称为边,节点也称为顶点,图表示的是多对多的关系。

无向图

2. 图的常用概念:

3. 图的表示方式:

4. 无向图的创建(邻接矩阵):

开篇所示的无向图,怎么用邻接矩阵代码实现呢?思路如下:

  A  B  C  D  E  F  G  H
A[0, 1, 0, 0, 0, 1, 0, 1]
B[1, 0, 1, 0, 0, 0, 1, 0]
C[0, 1, 0, 1, 1, 0, 0, 0]
D[0, 0, 1, 0, 0, 0, 0, 1]
E[0, 0, 1, 0, 0, 0, 1, 0]
F[1, 0, 0, 0, 0, 0, 1, 0]
G[0, 1, 0, 0, 1, 1, 0, 0]
H[1, 0, 0, 1, 0, 0, 0, 0]
public class UnDirectionGraph {

    private List<String> vertexList; // 存放顶点
    private int[][] edges; // 邻接矩阵,存放图的边
    private boolean[] isVisited; // 顶点是否被访问

    /**
     * 构造器
     * @param vertexCount 顶点的个数
     */
    public UnDirectionGraph(int vertexCount, List<String> vertex){
        this.vertexList = vertex;
        this.edges = new int[vertexCount][vertexCount];
        this.isVisited = new boolean[vertexCount];
    }

    /**
     * 构建无向图
     * @param vertex1 顶点1
     * @param vertex2 顶点2
     * @param isConnected 顶点1和顶点2是否连通,0:未连通 1:已连通
     */
    public void buildGraph(String vertex1, String vertex2, int isConnected){
        edges[vertexList.indexOf(vertex1)][vertexList.indexOf(vertex2)] = isConnected;
        edges[vertexList.indexOf(vertex2)][vertexList.indexOf(vertex1)] = isConnected;
    }

   /**
     * 打印邻接矩阵
     */
    public void printGraph(){
        for(int[] arr : edges){
            System.out.println(Arrays.toString(arr));
        }
    }
}

测试代码:

public static void main(String[] args) {
        int vertexCount = 8;
        List<String> vertexList = new ArrayList<>();
        vertexList.add("A");
        vertexList.add("B");
        vertexList.add("C");
        vertexList.add("D");
        vertexList.add("E");
        vertexList.add("F");
        vertexList.add("G");
        vertexList.add("H");

        UnDirectionGraph graph = new UnDirectionGraph(vertexCount, vertexList);
        graph.buildGraph("A", "B", 1);
        graph.buildGraph("A", "H", 1);
        graph.buildGraph("A", "F", 1);
        graph.buildGraph("B", "G", 1);
        graph.buildGraph("B", "C", 1);
        graph.buildGraph("C", "D", 1);
        graph.buildGraph("C", "E", 1);
        graph.buildGraph("D", "H", 1);
        graph.buildGraph("E", "G", 1);
        graph.buildGraph("F", "G", 1);
        graph.printGraph();
    }

5. 无向图的遍历:

(1). 遍历分类:

图的遍历分为两种:

(2). 深度优先算法步骤:

以开篇中的图为例:

  A  B  C  D  E  F  G  H
A[0, 1, 0, 0, 0, 1, 0, 1]

第一个1对应的是B,所以A的第一个邻接顶点是B,所以第二个遍历出来的是B,并且标记B为已访问;

  A  B  C  D  E  F  G  H
B[1, 0, 1, 0, 0, 0, 1, 0]

找到的是C,所以第三个遍历出来的是C,并且标记C为已访问;

 A  B  C  D  E  F  G  H
C[0, 1, 0, 1, 1, 0, 0, 0]

说白了就是这一行的D后面的那个1,就是E,打印E,并标记为已访问;

A --- B --- C --- D --- H --- E --- G --- F

其实概括地说就是:从第一个顶点开始,每次都选择该顶点的第一个邻接顶点往下走,走到死胡同的时候,就往回走,回到最后一次有岔路的地方,换另一条岔路走,又走到死胡同继续往回走……

(3). 深度优先遍历代码:

首先得在UnDirectionGraph类中加一个变量,用来表示该顶点有没有被访问过,如下:

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

然后在UnDirectionGraph中再增加如下方法:

   /**
     * 查找顶点vertex的第一个邻接顶点
     * @param vertex
     */
    public int findFstNeighbor(String vertex){
        // 拿到vertex的索引
        int vertexIndex = vertexList.indexOf(vertex);
        // 遍历二维数组的第 vertexIndex 行
        int[] arr = edges[vertexIndex];
        for (int i = 0; i < arr.length; i++) {
            // 如果arr[i] = 1,说明i对应的顶点就是vertex的邻接顶点
            if (arr[i] == 1){
                return i;
            }
        }
        return -1;
    }

    /**
     * 根据vertex的前一个邻接顶点priorVertexIndex,找到下一个邻接顶点
     * @param vertex
     * @param priorVertexIndex
     * @return
     */
    public int findNeighborByPriorNeighbor(String vertex, int priorVertexIndex){
        // 拿到vertex的索引
        int vertexIndex = vertexList.indexOf(vertex);
        // 从(priorVertexIndex + 1)开始遍历二维数组的第 vertexIndex 行
        int[] arr = edges[vertexIndex];
        for (int i = priorVertexIndex + 1; i < arr.length; i++) {
            if (arr[i] == 1){
                return i;
            }
        }
        return -1;
    }

    /**
     * 深度优先遍历
     * @param vertex
     */
    private void dfs(String vertex, boolean[] isVisited){
        // 打印当前顶点
        System.out.print(vertex + " --- ");
        // 标记当前顶点已经访问
        isVisited[vertexList.indexOf(vertex)] = true;
        // 找到当前顶点第一个邻接顶点
        int neighbor = findFstNeighbor(vertex);
        // 不等于-1,就打印,并且把第一个邻接顶点当成当前顶点,再去找它的第一个邻接顶点
        while (neighbor != -1){
            // 如果neighbor没有被访问过
            if (!isVisited[neighbor]){
                dfs(vertexList.get(neighbor), isVisited);
            } else {
                // 如果neighbor被访问过了,就找下一个邻接顶点
                neighbor = findNeighborByPriorNeighbor(vertex, neighbor);
            }
        }
        // 跳出循环,说明一条路走到底了,就应该开始回溯,怎么回溯?
        // 其实外面再写个方法,循环vertexList,让每个未被访问过的顶点都调用这个方法就好了
    }

    public void dfs() {
        for (String str : vertexList){
            if (!isVisited[vertexList.indexOf(str)]){
                dfs(str, isVisited);
            }
        }
    }

深度优先遍历的方法都写了详细注释,这里不再赘述。说一说找某个顶点的第一个邻接顶点(findFstNeighbor)和找某个顶点指定邻接顶点后面的那个邻接顶点(findNeighborByPriorNeighbor)这两个方法。

  A  B  C  D  E  F  G  H
A[0, 1, 0, 0, 0, 1, 0, 1]

A对应的就是这一行,第一个1出现的位置的索引是1,1在vertexList中对应的是B,所以A的第一个邻接顶点就是B

  A  B  C  D  E  F  G  H
A[0, 1, 0, 0, 0, 1, 0, 1]

这一行中,找到位置1后面的那个邻接顶点,即找到位置1后面的1第一次出现的位置的索引,显然对应的索引是5,在vertexList中对应的是F

上一篇 下一篇

猜你喜欢

热点阅读