圖論基礎入門

一.圖論與圖

1.什麼是圖論

在這裏插入圖片描述
圖論(英語:Graph theory),是組合數學的一個分支,和其他數學分支,如羣論、矩陣論、拓撲學有着密切關係。

2.什麼是圖

圖是圖論的主要研究對象。圖是由若干給定的頂點及連接兩頂點的邊所構成的圖形,這種圖形通常用來描述某些事物之間的某種特定關係。頂點用於代表事物,連接兩頂點的邊則用於表示兩個事物間具有這種關係。
在這裏插入圖片描述

3.經典圖論問題和應用

1.經典問題
  1. 四色問題
  2. 柯尼斯堡七橋問題
  3. 最小生成樹問題
  4. 路徑問題
2.應用

在這裏插入圖片描述

4.經典圖論算法

  1. Prim算法
  2. Krusk算法
  3. Dijkstra算法
  4. Bellman-Ford算法

二.圖的分類

1.無向圖

無向圖指的是圖的邊沒有方向的分別,通常可以認爲無向圖實質上是同時擁有兩個方向的有向圖。
在這裏插入圖片描述

2.有向圖

有向圖指的是圖的邊有方向的分別。
在這裏插入圖片描述

3.無權圖

無權圖指的是圖的邊不帶權重。
在這裏插入圖片描述

4.帶權圖

帶權圖指的是圖的邊擁有權重。
在這裏插入圖片描述

5.稀疏圖

在這裏插入圖片描述
稀疏圖指的是對於頂點總數來說,每個頂點的邊遠少於頂點總數。

6.稠密圖與完全圖

當大部分頂點的邊數接近於頂點總數時可被稱爲稠密圖;當每個頂點的邊都鏈接了其餘的節點,則被稱爲完全圖。
在這裏插入圖片描述

三.圖的連通性

1.連通性

在這裏插入圖片描述
上圖中,擁有三個圖的集合,它們之間並不是聯通的,但是自身卻是連通的。所以圖具有連通性。

2.帶環邊與平行邊

對於簡單圖來說,頂點與頂點之間只含有一條邊;而對於複雜圖來說,頂點之間的邊有可能會形成自環或平行邊:
在這裏插入圖片描述

自環邊可以用來表示一個導向自身的路徑,而平行邊則可以表示導向其他頂點的多個路徑。

四.圖的表示

1.鄰接矩陣

我們可以使用一個 V 乘 V 的布爾
二維數組來表示圖。 當頂點 v 和頂點 w 之間有相連接的邊
時, 定義 v 行 w 列的元素值爲 true, 否則爲
false。
在這裏插入圖片描述

  • Graph1
/**
 * @Auther: ARong
 * @Date: 2020/2/29 11:34 上午
 * @Description: 使用鄰接矩陣實現的圖結構
 */
public class Graph1 {
    private int n; // 頂點數
    private int m; // 邊數
    private boolean directed; // 是否爲有向圖
    private boolean[][] graph; // 二維boolean數組表示的圖結構

    public Graph1(int n, boolean directed) {
        assert n >= 0;
        this.n = n;
        this.directed = directed;
        this.m = 0;
        graph = new boolean[n][n];
    }

    /*
     * @Author ARong
     * @Description 向節點間添加一條邊
     * @Date 2020/2/29 11:39 上午
     * @Param [v, m]
     * @return void
     **/
    public void addEdge(int v, int m) {
        assert v >= 0 && v < n;
        assert m >= 0 && m < n;
        // 如果有這條邊則不添加
        if (hasEdge(v, m)) {
            return;
        }
        graph[v][m] = true;
        // 如果是無向圖,則還需要在另一方添加關係
        if (!directed) {
            graph[m][v] = true;
        }
        // 邊數增加
        m++;
    }

    /*
     * @Author ARong
     * @Description 判斷v,m是否存在邊
     * @Date 2020/2/29 11:42 上午
     * @Param [v, m]
     * @return boolean
     **/
    private boolean hasEdge(int v, int m) {
        return graph[v][m];
    }
}

使用鄰接矩陣來表示圖有許多缺點,例如:

  1. 空間需要提前聲明,如果圖的節點很多,那麼需要聲明的空間太大,且不能動態拓展
  2. 不能表示平行邊和自環關係

2.鄰接表

我們可以使用一個以頂點爲索引的列表數組, 其中的每個元素都是和該頂點相鄰的頂點列表。

使用鄰接表可以優化圖的空間大小,但是一些操作的時間會比鄰接矩陣實現的圖耗費時間多。

在這裏插入圖片描述
在這裏插入圖片描述

  • Graph2
/**
 * @Auther: ARong
 * @Date: 2020/2/29 11:55 上午
 * @Description: 使用鄰接表實現的圖結構
 */
public class Graph2 {

    private int n; // 頂點數
    private int m; // 邊數
    private boolean directed; // 是否爲有向圖
    private List<List<Integer>> graph; // 二維鏈表

    public Graph2(int n, boolean directed) {
        assert n >= 0;
        this.n = n;
        this.directed = directed;
        this.m = 0;
        graph = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            // 初始化
            graph.add(new ArrayList<>());
        }
    }


    /*
     * @Author ARong
     * @Description 向v,m之間添加一條邊
     * @Date 2020/2/29 12:01 下午
     * @Param [v, m]
     * @return void
     **/
    public void addEdge(int v, int m) {
        assert v >= 0 && v < n;
        assert m >= 0 && m < n;
        // 如果有這條邊則不添加
        if (hasEdge(v, m)) {
            return;
        }
        graph.get(v).add(m);
        // 如果是無向圖,則還需要在另一方添加關係
        if (!hasEdge(v, m) && !directed) {
            graph.get(m).add(n);
        }
        // 邊數增加
        m++;
    }

    /*
     * @Author ARong
     * @Description 判斷m,v是否存在邊
     * @Date 2020/2/29 11:58 上午
     * @Param [v, m]
     * @return boolean
     **/
    private boolean hasEdge(int v, int m) {
        List<Integer> list = graph.get(v);
        for (Integer node : list) {
            if (node == m) {
                return true;
            }
        }
        return false;
    }

}

在這裏插入圖片描述

五.圖的基本算法

0.基本代碼框架

以下是無向圖結構的基本代碼框架,一下算法實現基於該代碼框架

/**
 * @Auther: ARong
 * @Date: 2020/3/1 11:27 上午
 * @Description: 使用鄰接表實現的無向圖
 */
public class UndirectedGraph {
    // 頂點數量
    private int vertexNum;
    // 頂點邊數
    private int edgeNum;
    // 鄰接表
    private List<List<Integer>> adj;
    // 頂點信息
    private List<String> vertexInfo;

    /*
     * @Author ARong
     * @Description 傳入節點信息構造圖基本數據
     * @Date 2020/3/1 11:29 上午
     * @Param [vertexInfo]
     * @return
     **/
    public UndirectedGraph(List<String> vertexInfo) {
        this.vertexInfo = vertexInfo;
        this.vertexNum = vertexInfo.size();
        // 初始化圖
        adj = new ArrayList<>();
        for (int i = 0; i < vertexNum; i++) {
            // 構造每一個頂點的連接對象
            adj.add(new LinkedList<>());
        }
    }

    public UndirectedGraph(List<String> vertexInfo, int[][] edges) {
        this(vertexInfo);
        // 額外構造邊信息
        for (int[] twoVertex : edges) {
            addEdge(twoVertex[0], twoVertex[1]);
        }
    }

    public int vertexNum() {
        return vertexNum;
    }

    public int edgeNum() {
        return edgeNum;
    }

    /*
     * @Author ARong
     * @Description 添加邊
     * @Date 2020/3/1 11:31 上午
     * @Param [i, j]
     * @return void
     **/
    public void addEdge(int i, int j) {
        adj.get(i).add(j);
        adj.get(j).add(i);
        edgeNum++;
    }
    // 不需要set,所以不用返回List,返回可迭代對象就夠了
    public Iterable<Integer> adj(int i) {
        return adj.get(i);
    }

    /*
     * @Author ARong
     * @Description 獲取第i個頂點的數據
     * @Date 2020/3/1 11:32 上午
     * @Param [i]
     * @return java.lang.String
     **/
    public String getVertexInfo(int i) {
        return vertexInfo.get(i);
    }

    /*
     * @Author ARong
     * @Description 獲取第i個頂點的邊數
     * @Date 2020/3/1 11:32 上午
     * @Param [i]
     * @return int
     **/
    public int degree(int i) {
        return adj.get(i).size();
    }

    /*
     * @Author ARong
     * @Description 獲取頂點中最大的邊數
     * @Date 2020/3/1 11:33 上午
     * @Param []
     * @return int
     **/
    public int maxDegree() {
        int max = 0;
        for (int i = 0;i < vertexNum;i++) {
            if (degree(i) > max) {
                max = degree(i);
            }
        }
        return max;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(vertexNum).append("個頂點, ").append(edgeNum).append("條邊:\n");
        for (int i = 0; i < vertexNum; i++) {
            sb.append(i).append(": 【").append(adj.get(i)).append("】\n");
        }
        return sb.toString();
    }
}

1.深度優先遍歷

  /*
     * @Author ARong
     * @Description 對圖進行深度優先遍歷
     * @Date 2020/3/1 11:45 上午
     * @Param []
     * @return java.util.List<java.lang.Integer>
     **/
    private void dfs(int i, boolean[] visited) {
        visited[i] = true;
        // 獲取到i所連接的節點列表
        Iterator<Integer> iterator = adj(i).iterator();
        while (iterator.hasNext()) {
            Integer v = iterator.next();
            if (!visited[v]) {
                // v沒有被訪問過,繼續搜索
                dfs(v, visited);
            }
        }
    }

2.求解連通量

 /*
     * @Author ARong
     * @Description 獲取圖的連通分量
     * @Date 2020/3/1 12:23 下午
     * @Param []
     * @return java.util.List<java.lang.Integer>
     **/
    public int ccount() {
        int ccount = 0;
        // 初始化訪問數組,默認都是未訪問過
        boolean[] visited = new boolean[vertexNum];
        // 獲取圖的鄰接表
        for (int i = 0; i < adj.size(); i++) {
            if (!visited[i]) {
                // 在未訪問過的節點開始dfs
                dfs(i, visited);
                ccount++;
            }
        }
        return ccount;
    }

3.判斷兩個頂點是否連通

/*
     * @Author ARong
     * @Description 判斷兩個節點是否相互連通
     * @Date 2020/3/1 12:37 下午
     * @Param [a, b]
     * @return boolean
     **/
    private boolean isConnected(int a, int b) {
        // 先對a進行深度優先遍歷,標示遍歷過的節點,若此時b沒有被標示到說明不連通
        boolean[] visited = new boolean[vertexNum];
        dfs(a, visited);
        return visited[b];
    }

4.尋找連通路徑

/*
     * @Author ARong
     * @Description 找到a與b之間的一條連通路徑
     * @Date 2020/3/2 9:49 上午
     * @Param [a, b]
     * @return java.util.List<java.lang.Integer>
     **/
    public List<Integer> findPath(int a, int b) {
        // 判斷是否連通
        if (!isConnected(a, b)) {
            return null;
        }
        // 連通,通過dfs搜索,並使用數組記錄路徑
        HashMap<Integer, Integer> map = new HashMap<>();
        boolean[] visited = new boolean[vertexNum];
        dfs(a, b, visited, map);
        // 將路徑重現
        System.out.println(map.toString());
        LinkedList<Integer> res = new LinkedList<>();
        while (b != a) {
            res.offerFirst(b);
            b = map.get(b);
        }
        res.offerFirst(a);
        return res;
    }

    /*
     * @Author ARong
     * @Description dfs搜索a到b的路徑
     * @Date 2020/3/2 9:56 上午
     * @Param [a, b, visited, list]
     * @return void
     **/
    private void dfs(int a, int b, boolean[] visited, HashMap<Integer, Integer> map) {
        // dfs終止條件,找到b
        if (a == b) {
            return;
        }
        visited[a] = true;
        Iterator<Integer> iterator = adj(a).iterator();
        while (iterator.hasNext()) {
            Integer next = iterator.next();
            // 繼續dfs
            if(!visited[next]) {
                // 記錄來時路徑
                map.put(next, a);
                dfs(next, b, visited, map);
            }
        }
    }

6.廣度優先遍歷

/*
     * @Author ARong
     * @Description 廣度優先遍歷
     * @Date 2020/3/2 11:46 上午
     * @Param [i, visited]
     * @return void
     **/
     public void bfs(int i, boolean[] visited) {
         LinkedList<Integer> queue = new LinkedList<>();
         queue.offer(i);
         visited[i] = true;
         while (!queue.isEmpty()) {
             Integer poll = queue.poll();
             // 訪問標記
             Iterator<Integer> iterator = adj(poll).iterator();
             while (iterator.hasNext()) {
                 // 獲取poll的全部連接的頂點,繼續bfs
                 Integer next = iterator.next();
                 if (!visited[next]) {
                     visited[next] = true;
                     queue.offer(next);
                 }
             }
         }
     }

7.尋找最短路徑

/*
     * @Author ARong
     * @Description 獲取a和b之間的最短路徑
     * @Date 2020/3/2 11:57 上午
     * @Param [a, b]
     * @return java.util.List<java.lang.Integer>
     **/
    public List<Integer> findShortestPath(int a, int b) {
        // 判斷是否連通
        if (!isConnected(a, b)) {
            return null;
        }
        // 連通,通過dfs搜索,並使用數組記錄路徑
        HashMap<Integer, Integer> from = new HashMap<>();
        boolean[] visited = new boolean[vertexNum];
        bfs(a, b, visited, from);
        // 將路徑重現
        LinkedList<Integer> res = new LinkedList<>();
        while (b != a) {
            res.offerFirst(b);
            b = from.get(b);
        }
        res.offerFirst(a);
        return res;
    }

    /*
     * @Author ARong
     * @Description 廣度優先遍歷尋找最短路徑
     * @Date 2020/3/2 11:57 上午
     * @Param [a, b, visited, from]
     * @return void
     **/
    private void bfs(int a, int b, boolean[] visited, HashMap<Integer, Integer> from) {
        LinkedList<Integer> queue = new LinkedList<>();
        queue.offer(a);
        visited[a] = true;
        while (!queue.isEmpty()) {
            Integer poll = queue.poll();
            // 訪問標記
            Iterator<Integer> iterator = adj(poll).iterator();
            while (iterator.hasNext()) {
                // 獲取poll的全部連接的頂點,繼續bfs
                Integer next = iterator.next();
                if (!visited[next]) {
                    // 記錄來時路徑
                    from.put(next, poll);
                    // 判斷是否找到
                    if (next == b) {
                        return;
                    }
                    visited[next] = true;
                    queue.offer(next);
                }
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章