數據結構之圖:鄰接矩陣和鄰接表、深度優先遍歷和廣度優先遍歷

簡介

線性表是一種線性結構,除了頭結點和尾節點,線性表的每個元素都只有一個前取節點和一個後繼節點。而樹結構則相較於線性表更加複雜,它描述的關係爲數據元素之間的父子關係,也是現實世界父子關係的縮影,

  • 一個父親節點可以有零個或者多個子節點
  • 而每個子節點有且只有一個父節點

但是在圖是比樹更加複雜的數據結構,圖的基本特徵是,在圖中,數據元素(頂點)之間的關係使任意的,每個頂點都可以和其他任何頂點相關

上圖中就存在兩個圖,其中右邊的圖更加複雜,是現實世界中的交通路線圖。左圖是抽象圖,可以看到在左圖是一個無向圖,其中

  • 頂點集合爲{A、B、C、D、E}
  • 邊集爲{A-B, B-C, A-D, D-E, B-C}一共五條邊
    • 注意:在無向圖中A-B和B-A是一樣的。

定義

圖G由兩個集合V和E組成,記爲G={V, E}。其中V是頂點的有限集合,E是連接V中兩個不同頂點的邊的有限集合。如果E中的頂點對是有序的,即E中的每條邊都是有方向的,則稱G是有向圖。如果頂點對是無序的,則稱G是無向圖。

圖的常用概念

圖的存儲方式

圖有兩種存儲方式:鄰接矩陣(二維數組)和鄰接表(鏈表),選取那種存儲方式取決於具體的圖結構和欲實施的操作。

鄰接矩陣

1592622289383

轉化成鄰接矩陣,則表示如下:

1592622396024

鄰接矩陣表示法是把鄰接矩陣的n行表示成n個單鏈表。其中較爲關鍵的是邊鏈表的概念

與頂點v鄰接的所有頂點以某種次序組成的單鏈表稱作頂點v的邊鏈表。

鄰接矩陣中Graph類的聲明如下圖所示:

鄰接表

代碼實現

鄰接矩陣

鄰接矩陣代碼實現

上述的UML圖對應的代碼如下:

package com.atguigu.graph.graph;

import java.awt.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

/**
 * 使用鄰接矩陣實現圖類
 *
 * @author songquanheng
 * @Time: 2020/6/20-11:32
 */
public class Graph {
    /**
     * 頂點數組
     */
    private String[] vertexs;
    private int numberOfVertex;
    /**
     * 邊數
     */
    private int numberOfEdges;


    /**
     * 邊集合,採用二維數組表示
     */
    private int[][] edges;

    public static void main(String[] args) {
        String[] vertexs = "1 2 3 4 5 6 7 8 ".split(" ");
        Graph graph = new Graph(vertexs);
        graph.show();

        graph.insertEdge(0, 1, 1);
        graph.insertEdge(0, 2, 1);
        graph.insertEdge(1, 3, 1);
        graph.insertEdge(1, 4, 1);
        graph.insertEdge(3, 7, 1);
        graph.insertEdge(4, 7, 1);
        graph.insertEdge(2, 5, 1);
        graph.insertEdge(2, 6, 1);
        graph.insertEdge(5, 6, 1);

        graph.show();

        graph.dfs();
        System.out.println();
        graph.dfs(2);
        System.out.println();

        graph.bfs();
        System.out.println();

        graph.bfs(2);
        System.out.println();
    }

    public Graph(String[] vertexs) {
        numberOfVertex = vertexs.length;
        this.vertexs = new String[numberOfVertex];
        int i = 0;
        for (String item : vertexs) {
            this.vertexs[i++] = item;
        }

        // 初始化鄰接矩陣
        this.edges = new int[numberOfVertex][numberOfVertex];

    }

    public void show() {
        System.out.println("Graph.show");
        System.out.println(Arrays.toString(vertexs));
        System.out.println();
        for (int[] row : edges) {
            System.out.println(Arrays.toString(row));
        }
        System.out.println("graph.getNumberOfEdges() = " + getNumberOfEdges());
        System.out.println("graph.getNumberOfVertex() = " + getNumberOfVertex());
        System.out.println();
    }

    /**
     * @param v1 邊的起點的序號
     * @param v2 邊的終點的序號
     * @param w  邊的權值 無向圖賦值爲1即可
     */
    public void insertEdge(int v1, int v2, int w) {
        edges[v1][v2] = w;
        edges[v2][v1] = w;
        numberOfEdges++;
    }

    /**
     * 深度優先遍歷,此時不考慮起始點,即以0號序列的頂點爲起始頂點
     */
    public void dfs() {
        System.out.println("Graph.dfs");
        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);
        for (int i = 0; i < numberOfVertex; i++) {
            if (!visited[i]) {
                dfs(i, visited);
            }

        }
        System.out.println();
    }

    /**
     * 從指定頂點進行深度優先遍歷
     *
     * @param vertex 開始頂點的序號
     */
    public void dfs(int vertex) {
        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);

        dfs(vertex, visited);
        System.out.println();
    }

    /**
     * @param vertex 深度優先遍歷的開始頂點所在的序號
     */
    private void dfs(int vertex, boolean[] visited) {
        System.out.print(vertexs[vertex] + "->");
        visited[vertex] = true;

        int w = getFirstNeighbour(vertex);
        while (w != -1) {
            if (!visited[w]) {
                dfs(w, visited);
            } else {
                // 如果w已經被訪問過,則訪問w的下一個鄰接頂點
                w = getNextNeighbour(vertex, w);
            }

        }

    }

    /**
     * 廣度優先遍歷
     */
    public void bfs() {
        System.out.println("Graph.bfs");

        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);

        for (int i = 0; i < numberOfVertex; i++) {
            if (!visited[i]) {
                bfs(i, visited);
            }
        }
    }

    /**
     * 從指定頂點vertex開始進行廣度優先遍歷
     *
     * @param vertex 從vertex頂點開始進行廣度優先遍歷
     */
    public void bfs(int vertex) {
        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);

        bfs(vertex, visited);

    }

    /**
     * 從頂點vertex開始進行廣度優先遍歷
     *
     * @param vertex  頂點序號
     * @param visited 輔助遍歷數組
     */
    private void bfs(int vertex, boolean[] visited) {
        System.out.print(vertexs[vertex] + "->");
        visited[vertex] = true;

        LinkedList<Integer> queue = new LinkedList<>();
        queue.addLast(vertex);
        while (!queue.isEmpty()) {
            // 此時head所在的頂點已經訪問過了
            int head = queue.remove();
            int w = getFirstNeighbour(head);

            while (w != -1) {
                if (!visited[w]) {
                    // 深度優先遍歷從此處開始遞歸,但廣度優先不進行遞歸
                    System.out.print(vertexs[w] + "->");
                    visited[w] = true;
                    queue.addLast(w);
                }
                w = getNextNeighbour(head, w);
            }
        }


    }


    /**
     * 返回序號爲vertex的第一個鄰接頂點的序號
     *
     * @param vertex 頂點的序號,對於A頂點,則傳入的vertex爲A頂點所在的序號0
     * @return 返回該頂點的第一個鄰接頂點所在的序號, 如果存在,返回頂點所在的序號,否則返回-1表示不存在
     */
    public int getFirstNeighbour(int vertex) {
        return neighbour(vertex, 0);
    }

    /**
     * 返回序號爲vertex的頂點相對於序號爲currentAdjacentVertex的頂點的下一個鄰接頂點的序號
     *
     * @param vertex                頂點序號
     * @param currentAdjacentVertex currentAdjacentVertex爲vertex序號頂點的鄰接點,求相對於這個currentAdjacentVertex的下一個鄰接頂點的序號
     * @return 返回下一個鄰接頂點的序號
     */
    public int getNextNeighbour(int vertex, int currentAdjacentVertex) {
        return neighbour(vertex, currentAdjacentVertex + 1);
    }

    /**
     * 從firstSearchLocation查找獲取頂點vertex序號的頂點的鄰接點的序號,
     *
     * @param vertex           頂點序號
     * @param firstSearchIndex 查找位置值的範圍爲[0, numberOfVertex - 1]
     * @return 如果從firstSearchIndex開始查找存在返回鄰接頂點,則返回鄰接頂點的序號,否則返回1
     */
    private int neighbour(int vertex, int firstSearchIndex) {
        for (int i = firstSearchIndex; i < numberOfVertex; i++) {
            if (edges[vertex][i] > 0) {
                return i;
            }
        }
        return -1;
    }

    public int getNumberOfEdges() {
        return numberOfEdges;
    }

    public int getNumberOfVertex() {
        return numberOfVertex;
    }


}

深度優先遍歷闡述

深度優先遍歷的思想如下:

因爲圖的人一頂點都可能與其他頂點相連接,所以圖的遍歷算法比樹的遍歷算法要複雜的多。在訪問了某個頂點之後,可能沿着某條路徑搜索又回到了出發點。因此,爲保證每個頂點恰好只訪問一次,需要額外設置一個標誌頂點是否被訪問的輔助數組visited。該數組元素的初值均爲false,一旦頂點vi被訪問,相應的visited[i]置位true。圖的遍歷算法應用非常廣泛,能夠解決很多與圖有關的問題,如判斷兩個頂點之間是否可達,確定某一頂點所屬的聯通分量,構造圖的支撐樹等

深度優先代碼實現

 /**
     * 深度優先遍歷,此時不考慮起始點,即以0號序列的頂點爲起始頂點
     */
    public void dfs() {
        System.out.println("Graph.dfs");
        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);
        for (int i = 0; i < numberOfVertex; i++) {
            if (!visited[i]) {
                dfs(i, visited);
            }

        }
        System.out.println();
    }

    /**
     * 從指定頂點進行深度優先遍歷
     *
     * @param vertex 開始頂點的序號
     */
    public void dfs(int vertex) {
        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);

        dfs(vertex, visited);
        System.out.println();
    }

    /**
     * @param vertex 深度優先遍歷的開始頂點所在的序號
     */
    private void dfs(int vertex, boolean[] visited) {
        System.out.print(vertexs[vertex] + "->");
        visited[vertex] = true;

        int w = getFirstNeighbour(vertex);
        while (w != -1) {
            if (!visited[w]) {
                dfs(w, visited);
            } else {
                // 如果w已經被訪問過,則訪問w的下一個鄰接頂點
                w = getNextNeighbour(vertex, w);
            }

        }
    }

深度優先遍歷在實現時,關鍵的幾個步驟爲:

  1. 初始化輔助數組,均置爲false
  2. 標誌初始頂點被訪問,並獲取該頂點的第一個鄰接頂點,作爲當前的鄰接頂點
  3. 循環,判斷該鄰接頂點是否被訪問過,如果沒有被訪問過,則以相同的策略訪問鄰接頂點,否則獲取初始頂點的相對於當前鄰接頂點獲取下一個鄰接頂點繼續進行深度優先遍歷

廣度優先遍歷闡述

廣度優先遍歷代碼實現

/**
     * 廣度優先遍歷
     */
    public void bfs() {
        System.out.println("Graph.bfs");

        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);

        for (int i = 0; i < numberOfVertex; i++) {
            if (!visited[i]) {
                bfs(i, visited);
            }
        }
    }

    /**
     * 從指定頂點vertex開始進行廣度優先遍歷
     *
     * @param vertex 從vertex頂點開始進行廣度優先遍歷
     */
    public void bfs(int vertex) {
        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);

        bfs(vertex, visited);

    }

    /**
     * 從頂點vertex開始進行廣度優先遍歷
     *
     * @param vertex  頂點序號
     * @param visited 輔助遍歷數組
     */
    private void bfs(int vertex, boolean[] visited) {
        System.out.print(vertexs[vertex] + "->");
        visited[vertex] = true;

        LinkedList<Integer> queue = new LinkedList<>();
        queue.addLast(vertex);
        while (!queue.isEmpty()) {
            // 此時head所在的頂點已經訪問過了
            int head = queue.remove();
            int w = getFirstNeighbour(head);

            while (w != -1) {
                if (!visited[w]) {
                    // 深度優先遍歷從此處開始遞歸,但廣度優先不進行遞歸
                    System.out.print(vertexs[w] + "->");
                    visited[w] = true;
                    queue.addLast(w);
                }
                w = getNextNeighbour(head, w);
            }
        }


    }

鄰接表

鄰接表代碼實現

鄰接表的代碼實現如下所示:

package com.atguigu.graph.graph;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;

/**
 * 鄰接表實現圖
 *
 * @author songquanheng
 */
public class GraphList {
    private int numberOfVertex;
    private int numberOfEdge;

    /**
     * 頂點集合
     */
    private Vertex[] vertices;

    public static void main(String[] args) {
        GraphList graphList = new GraphList();
        graphList.show();

        // 8 1 2 3 4 5 6 7 8
        graphList.initializeVertex();
        // 9 1 2 1 1 3 1 2 4 1 2 5 1 3 6 1 3 7 1 6 7 1 4 8 1 5 8 1
        graphList.initializeEdge();
        graphList.show();
        System.out.println();

        graphList.dfs(0);
        graphList.dfs("1");
        System.out.println();

        graphList.bfs(0);
        graphList.bfs("1");


    }

    /**
     * 解決離散圖像-即非連通圖像的深度優先遍歷
     */
    public void dfs() {
        System.out.println("GraphList.dfs");

        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);
        for (int i = 0; i < getNumberOfVertex(); i++) {
            if (!visited[i]) {
                dfs(i, visited);
            }
        }

        System.out.println();
    }

    public void dfs(String vertexName) {
        System.out.println("GraphList.dfs");
        System.out.println("vertexName = [" + vertexName + "]");


        int vertex = vertexIndexByName(vertexName);
        dfs(vertex);
        System.out.println();
    }

    /**
     * 從指定頂點進行深度優先遍歷,對於連通的圖可以完整的遍歷
     *
     * @param vertex 開始頂點的序號
     */
    public void dfs(int vertex) {
        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);

        dfs(vertex, visited);
        System.out.println();
    }

    /**
     * @param vertex 深度優先遍歷的開始頂點所在的序號
     */
    private void dfs(int vertex, boolean[] visited) {
        System.out.print(vertices[vertex].getVertexName() + "->");
        visited[vertex] = true;

        int w = getFirstNeighbour(vertex);
        while (w != -1) {
            if (!visited[w]) {
                dfs(w, visited);
            } else {
                // 如果w已經被訪問過,則訪問w的下一個鄰接頂點
                w = getNextNeighbour(vertex, w);
            }

        }

    }

    public void bfs(String vertexName) {
        System.out.println("GraphList.bfs");
        System.out.println("vertexName = [" + vertexName + "]");

        int vertex = vertexIndexByName(vertexName);

        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);

        bfs(vertex, visited);
        System.out.println();
    }
    /**
     * 從指定頂點開始進行廣度優先遍歷
     * @param vertex 頂點所在的位置
     */
    public void bfs(int vertex) {
        System.out.println("GraphList.bfs");

        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);

        bfs(vertex, visited);
        System.out.println();
    }

    private void bfs(int vertex, boolean[] visited) {
        System.out.print(vertices[vertex].getVertexName() + "->");
        visited[vertex] = true;

        LinkedList<Integer> queue = new LinkedList<>();
        queue.addLast(vertex);

        while (!queue.isEmpty()) {
            traverseVertex(visited, queue);
        }

        System.out.println();
    }

    /**
     * @param visited 訪問的輔助數組
     * @param queue 待訪問的頂點隊列
     */
    private void traverseVertex(boolean[] visited, LinkedList<Integer> queue) {
        int vertex = queue.remove();
        int neighbour = getFirstNeighbour(vertex);

        // 一個循環走完,就把一行走位了,在這一行中,頂點vertexIndex不變,而neighbour作爲vertexIndex的鄰接頂點一直在遍歷
        // 然後在下一個循環中,vertexIndex更新成第一個頂點的鄰接頂點了
        while (neighbour != -1) {
            if (!visited[neighbour]) {
                System.out.print(vertices[neighbour].getVertexName() + "->");
                visited[neighbour] = true;
                queue.addLast(neighbour);
            }
            neighbour = getNextNeighbour(vertex, neighbour);
        }
    }

    /**
     * 負責圖的初始化
     */
    public void initializeVertex() {
        System.out.println("GraphList.initializeVertex");
        Scanner scan = new Scanner(System.in);
        System.out.println("請輸入頂點數");
        this.numberOfVertex = scan.nextInt(); // 8

        this.vertices = new Vertex[this.numberOfVertex];
        System.out.println("請依次輸入頂點名稱:");

        for (int i = 0; i < numberOfVertex; i++) {
            String name = scan.next(); // 1 2 3 4 5 6 7 8
            Vertex vertex = new Vertex(name);
            vertices[i] = vertex;
        }

        System.out.println("頂點初始化完成");
    }

    public void initializeEdge() {
        System.out.println("GraphList.initializeEdge");
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入頂點數"); // 9
        this.numberOfEdge = scanner.nextInt();


        for (int i = 0; i < this.numberOfEdge; i++) {
            System.out.println("開始輸入邊,形式爲(頂點1名稱 頂點2名稱 權重)");
            // 此處應校驗頂點名稱是合法的,即用戶輸入的頂點在之前的初始化頂點中
            // 1 2 1 1 3 1 2 4 1 2 5 1 3 6 1 3 7 1 6 7 1 4 8 1 5 8 1
            String vertex1Name = scanner.next();
            String vertex2Name = scanner.next();
            int weight = scanner.nextInt();

            int vertex1Index = vertexIndexByName(vertex1Name);
            int vertex2Index = vertexIndexByName(vertex2Name);

            Edge edge1 = new Edge(vertex2Index, weight);
            vertices[vertex1Index].add(edge1);

            Edge edge2 = new Edge(vertex1Index, weight);
            vertices[vertex2Index].add(edge2);

        }
    }

    /**
     * @param index 頂點序號
     * @return 返回頂點序號的第一個鄰接頂點的序號
     */
    public int getFirstNeighbour(int index) {
        Edge edge = vertices[index].getAdjacentEdge();
        if ((edge != null)) {
            return edge.getAdjacentVertex();
        }
        return -1;
    }

    /**
     * 獲取index頂點,相對於currentNeighbourIndex的下一個鄰接頂點
     *
     * @param index                 頂點序號
     * @param currentNeighbourIndex 在頂點序號的鄰接頂點的當前鄰接頂點
     * @return 相對於currentNeighbourIndex的,index頂點的下一個鄰接頂點
     */
    public int getNextNeighbour(int index, int currentNeighbourIndex) {
        Edge edge = vertices[index].getAdjacentEdge();

        while (edge != null && edge.getAdjacentVertex() != currentNeighbourIndex) {
            edge = edge.getNext();
        }

        edge = edge.getNext();
        if (edge == null) {
            return -1;
        } else {
            return edge.getAdjacentVertex();
        }


    }

    /**
     * 輸出圖的鄰接鏈表表示
     */
    public void show() {
        System.out.println("GraphList.show");
        System.out.println("numberOfVertex = " + getNumberOfVertex());
        System.out.println("numberOfEdge = " + getNumberOfEdge());

        Edge edge;
        for (int i = 0; i < numberOfVertex; i++) {
            System.out.print(vertices[i].vertexName);

            edge = vertices[i].adjacentEdge;
            while (edge != null) {
                // edge.getAdjacentVertex() 獲取邊上的鄰接頂點
                // vertices[edge.getAdjacentVertex()].getVertexName()
                System.out.print("-->" + vertexNameByIndex(edge.getAdjacentVertex()));
                edge = edge.getNext();
            }
            System.out.println();
        }
        System.out.println();
    }

    /**
     * @param index 根據頂點的索引號獲取頂點的名稱
     * @return 頂點名稱
     */
    public String vertexNameByIndex(int index) {
        assert index < vertices.length;

        return vertices[index].getVertexName();
    }

    /**
     * 根據頂點名稱返回頂點的索引號
     *
     * @param vertexName 頂點名稱
     * @return 返回的是邊對象的標籤
     */
    public int vertexIndexByName(String vertexName) {
        List<String> validVertexNames = Arrays.stream(vertices)
                .map(vertex -> vertex.getVertexName())
                .collect(Collectors.toList());
        assert validVertexNames.contains(vertexName);

        for (int i = 0; i < getNumberOfVertex(); i++) {
            if (vertices[i].vertexName.equals(vertexName)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * @return 獲取圖中的頂點數
     */
    public int getNumberOfVertex() {
        return numberOfVertex;
    }

    /**
     * @return 獲取圖中的邊數
     */
    public int getNumberOfEdge() {
        return numberOfEdge;
    }
}

/**
 * 邊類
 */
class Edge {
    /**
     * 鄰接頂點
     */
    private int adjacentVertex;
    /**
     * 權重
     */
    private int weight;
    /**
     * 下一個鄰接頂點
     */
    private Edge next;

    public Edge getNext() {
        return next;
    }

    public void setNext(Edge next) {
        this.next = next;
    }

    public Edge(int adjacentVertex, int weight) {
        this.adjacentVertex = adjacentVertex;

        this.weight = weight;
    }

    public int getAdjacentVertex() {
        return adjacentVertex;
    }
}

/**
 * 頂點類
 */
class Vertex {
    /**
     * 頂點名稱
     */
    String vertexName;
    /**
     * 邊鏈表的頭指針
     */
    Edge adjacentEdge;

    public Vertex(String vertexName) {
        this.vertexName = vertexName;
    }

    public String getVertexName() {
        return vertexName;
    }

    public void setVertexName(String vertexName) {
        this.vertexName = vertexName;
    }

    public Edge getAdjacentEdge() {
        return adjacentEdge;
    }

    public void setAdjacentEdge(Edge adjacentEdge) {
        this.adjacentEdge = adjacentEdge;
    }

    /**
     * 把待添加的鄰接邊添加到末尾
     *
     * @param edge 待添加的鄰接邊
     */
    public void add(Edge edge) {
        // 表示待添加的鄰接表爲第一條邊
        if (adjacentEdge == null) {
            adjacentEdge = edge;
        } else {
            Edge tail = tail();
            tail.setNext(edge);

        }
    }

    /**
     * 獲取當前頂點的鏈表的尾部, 在該頂點的鄰接表不爲空的情況下調用
     *
     * @return 獲取鄰接表的尾部
     */
    private Edge tail() {
        assert adjacentEdge != null;
        Edge tail = adjacentEdge;
        while (tail.getNext() != null) {
            tail = tail.getNext();
        }
        return tail;
    }


}

代碼運行結果

GraphList.show
numberOfVertex = 0
numberOfEdge = 0

GraphList.initializeVertex
璇瘋緭鍏ラ《鐐規暟
8 1 2 3 4 5 6 7 8
璇蜂緷嬈¤緭鍏ラ《鐐瑰悕縐幫細
欏剁偣鍒濆鍖栧畬鎴�
GraphList.initializeEdge
璇瘋緭鍏ラ《鐐規暟
9 1 2 1 1 3 1 2 4 1 2 5 1 3 6 1 3 7 1 6 7 1 4 8 1 5 8 1
寮�濮嬭緭鍏ヨ竟錛屽艦寮忎負(欏剁偣1鍚嶇О 欏剁偣2鍚嶇О 鏉冮噸)
寮�濮嬭緭鍏ヨ竟錛屽艦寮忎負(欏剁偣1鍚嶇О 欏剁偣2鍚嶇О 鏉冮噸)
寮�濮嬭緭鍏ヨ竟錛屽艦寮忎負(欏剁偣1鍚嶇О 欏剁偣2鍚嶇О 鏉冮噸)
寮�濮嬭緭鍏ヨ竟錛屽艦寮忎負(欏剁偣1鍚嶇О 欏剁偣2鍚嶇О 鏉冮噸)
寮�濮嬭緭鍏ヨ竟錛屽艦寮忎負(欏剁偣1鍚嶇О 欏剁偣2鍚嶇О 鏉冮噸)
寮�濮嬭緭鍏ヨ竟錛屽艦寮忎負(欏剁偣1鍚嶇О 欏剁偣2鍚嶇О 鏉冮噸)
寮�濮嬭緭鍏ヨ竟錛屽艦寮忎負(欏剁偣1鍚嶇О 欏剁偣2鍚嶇О 鏉冮噸)
寮�濮嬭緭鍏ヨ竟錛屽艦寮忎負(欏剁偣1鍚嶇О 欏剁偣2鍚嶇О 鏉冮噸)
寮�濮嬭緭鍏ヨ竟錛屽艦寮忎負(欏剁偣1鍚嶇О 欏剁偣2鍚嶇О 鏉冮噸)
GraphList.show
numberOfVertex = 8
numberOfEdge = 9
1-->2-->3
2-->1-->4-->5
3-->1-->6-->7
4-->2-->8
5-->2-->8
6-->3-->7
7-->3-->6
8-->4-->5


1->2->4->8->5->3->6->7->
GraphList.dfs
vertexName = [1]
1->2->4->8->5->3->6->7->


GraphList.bfs
1->2->3->4->5->6->7->8->

GraphList.bfs
vertexName = [1]
1->2->3->4->5->6->7->8->

初始化(對頂點的初始化和對邊的初始化)

在處理圖的初始化的時候,採用和用戶進行交互的方式,採用的方式是先進行頂點初始化、然後進行邊的初始化。

通過Scanner類進行初始化。

頂點初始化

/**
     * 負責圖的初始化
     */
    public void initializeVertex() {
        System.out.println("GraphList.initializeVertex");
        Scanner scan = new Scanner(System.in);
        System.out.println("請輸入頂點數");
        this.numberOfVertex = scan.nextInt(); // 8

        this.vertices = new Vertex[this.numberOfVertex];
        System.out.println("請依次輸入頂點名稱:");

        for (int i = 0; i < numberOfVertex; i++) {
            String name = scan.next(); // 1 2 3 4 5 6 7 8
            Vertex vertex = new Vertex(name);
            vertices[i] = vertex;
        }

        System.out.println("頂點初始化完成");
    }

可以看到在圖的內部是通過vertices數組來管理頂點的。

邊的初始化

public void initializeEdge() {
        System.out.println("GraphList.initializeEdge");
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入頂點數"); // 9
        this.numberOfEdge = scanner.nextInt();


        for (int i = 0; i < this.numberOfEdge; i++) {
            System.out.println("開始輸入邊,形式爲(頂點1名稱 頂點2名稱 權重)");
            // 此處應校驗頂點名稱是合法的,即用戶輸入的頂點在之前的初始化頂點中
            // 1 2 1 1 3 1 2 4 1 2 5 1 3 6 1 3 7 1 6 7 1 4 8 1 5 8 1
            String vertex1Name = scanner.next();
            String vertex2Name = scanner.next();
            int weight = scanner.nextInt();

            int vertex1Index = vertexIndexByName(vertex1Name);
            int vertex2Index = vertexIndexByName(vertex2Name);

            Edge edge1 = new Edge(vertex2Index, weight);
            vertices[vertex1Index].add(edge1);

            Edge edge2 = new Edge(vertex1Index, weight);
            vertices[vertex2Index].add(edge2);

        }
}

	/**
     * @param index 根據頂點的索引號獲取頂點的名稱
     * @return 頂點名稱
     */
    public String vertexNameByIndex(int index) {
        assert index < vertices.length;

        return vertices[index].getVertexName();
    }

    /**
     * 根據頂點名稱返回頂點的索引號
     *
     * @param vertexName 頂點名稱
     * @return 返回的是邊對象的標籤
     */
    public int vertexIndexByName(String vertexName) {
        List<String> validVertexNames = Arrays.stream(vertices)
                .map(vertex -> vertex.getVertexName())
                .collect(Collectors.toList());
        assert validVertexNames.contains(vertexName);

        for (int i = 0; i < getNumberOfVertex(); i++) {
            if (vertices[i].vertexName.equals(vertexName)) {
                return i;
            }
        }
        return -1;
    }


輔助函數

    /**
     * @param index 頂點序號
     * @return 返回頂點序號的第一個鄰接頂點的序號
     */
    public int getFirstNeighbour(int index) {
        Edge edge = vertices[index].getAdjacentEdge();
        if ((edge != null)) {
            return edge.getAdjacentVertex();
        }
        return -1;
    }

    /**
     * 獲取index頂點,相對於currentNeighbourIndex的下一個鄰接頂點
     *
     * @param index                 頂點序號
     * @param currentNeighbourIndex 在頂點序號的鄰接頂點的當前鄰接頂點
     * @return 相對於currentNeighbourIndex的,index頂點的下一個鄰接頂點
     */
    public int getNextNeighbour(int index, int currentNeighbourIndex) {
        Edge edge = vertices[index].getAdjacentEdge();

        while (edge != null && edge.getAdjacentVertex() != currentNeighbourIndex) {
            edge = edge.getNext();
        }

        edge = edge.getNext();
        if (edge == null) {
            return -1;
        } else {
            return edge.getAdjacentVertex();
        }


    }

深度優先遍歷實現

深度優先遍歷的思想與之前一樣的,無多大的區別

    /**
     * 解決離散圖像-即非連通圖像的深度優先遍歷
     */
    public void dfs() {
        System.out.println("GraphList.dfs");

        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);
        for (int i = 0; i < getNumberOfVertex(); i++) {
            if (!visited[i]) {
                dfs(i, visited);
            }
        }

        System.out.println();
    }

    public void dfs(String vertexName) {
        System.out.println("GraphList.dfs");
        System.out.println("vertexName = [" + vertexName + "]");


        int vertex = vertexIndexByName(vertexName);
        dfs(vertex);
        System.out.println();
    }

    /**
     * 從指定頂點進行深度優先遍歷,對於連通的圖可以完整的遍歷
     *
     * @param vertex 開始頂點的序號
     */
    public void dfs(int vertex) {
        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);

        dfs(vertex, visited);
        System.out.println();
    }

    /**
     * @param vertex 深度優先遍歷的開始頂點所在的序號
     */
    private void dfs(int vertex, boolean[] visited) {
        System.out.print(vertices[vertex].getVertexName() + "->");
        visited[vertex] = true;

        int w = getFirstNeighbour(vertex);
        while (w != -1) {
            if (!visited[w]) {
                dfs(w, visited);
            } else {
                // 如果w已經被訪問過,則訪問w的下一個鄰接頂點
                w = getNextNeighbour(vertex, w);
            }

        }

    }

廣度優先代碼實現

/**
     * 從指定頂點名稱開始進行廣度優先遍歷
     *
     * @param vertexName 頂點名稱
     */
    public void bfs(String vertexName) {
        System.out.println("GraphList.bfs");
        System.out.println("vertexName = [" + vertexName + "]");

        int vertex = vertexIndexByName(vertexName);

        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);

        bfs(vertex, visited);
        System.out.println();
    }

    /**
     * 從指定頂點開始進行廣度優先遍歷
     *
     * @param vertex 頂點所在的位置
     */
    public void bfs(int vertex) {
        System.out.println("GraphList.bfs");

        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);

        bfs(vertex, visited);
        System.out.println();
    }

    private void bfs(int vertex, boolean[] visited) {
        System.out.print(vertices[vertex].getVertexName() + "->");
        visited[vertex] = true;

        LinkedList<Integer> queue = new LinkedList<>();
        queue.addLast(vertex);

        while (!queue.isEmpty()) {
            traverseVertex(visited, queue);
        }

        System.out.println();
    }

    /**
     * @param visited 訪問的輔助數組
     * @param queue   待訪問的頂點隊列
     */
    private void traverseVertex(boolean[] visited, LinkedList<Integer> queue) {
        int vertex = queue.remove();
        int neighbour = getFirstNeighbour(vertex);

        // 一個循環走完,就把一行走位了,在這一行中,頂點vertexIndex不變,而neighbour作爲vertexIndex的鄰接頂點一直在遍歷
        // 然後在下一個循環中,vertexIndex更新成第一個頂點的鄰接頂點了
        while (neighbour != -1) {
            if (!visited[neighbour]) {
                System.out.print(vertices[neighbour].getVertexName() + "->");
                visited[neighbour] = true;
                queue.addLast(neighbour);
            }
            neighbour = getNextNeighbour(vertex, neighbour);
        }
    }

注意事項

 需要注意的是,無論是深度優先遍歷還是廣度優先遍歷,對於非連通圖來說,通過指定頂點進行遍歷是是無法遍歷完所有的頂點的。因此無論是進行深度優先遍歷、還是廣度優先遍歷,均提供了不提供指定頂點開始的遍歷方式,如下圖代碼所示:

	/**
     * 深度優先遍歷,此時不考慮起始點,即以0號序列的頂點爲起始頂點
     */
    public void dfs() {
        System.out.println("Graph.dfs");
        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);
        for (int i = 0; i < numberOfVertex; i++) {
            if (!visited[i]) {
                dfs(i, visited);
            }

        }
        System.out.println();
    }

	/**
     * 廣度優先遍歷
     */
    public void bfs() {
        System.out.println("Graph.bfs");

        boolean[] visited = new boolean[numberOfVertex];
        Arrays.fill(visited, false);

        for (int i = 0; i < numberOfVertex; i++) {
            if (!visited[i]) {
                bfs(i, visited);
            }
        }
    }

下載

下載地址

總結

 總算是結束了對本文的撰寫,2020年第25週週末,自己好像也就完成了這一個文章的撰寫,其實效率很低的,換了大的顯示器以後,辦理了網線發現更容易看LPL了,很多的時間都花費掉了,好在週末的這兩天都進行了運動,雖然雨一下一整天,但兩天早晨都進行了跳繩,算是一點小小的堅持,只是體重仍然沒有起色。看來想要的東西,從來沒有能夠簡簡單單就獲得到的。

 關於圖的基礎知識,先介紹到這裏,之後還要進行闡述的內容包括

  • 圖的深度優先查找、廣度優先查找
  • 最小支撐樹
    • 普利姆算法
    • 克魯斯卡爾算法
  • 地接斯科拉算法
  • 弗洛伊德算法

 雖然讀書時關於圖的基礎內容自己理解的深度不夠,但現在韓順平老師的數據結構的課程已經看完了,自己要趁着機會好好的總結一下。

2020年6月21日週日22:02:18於AUX

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章