首頁>技術>

出處:https://segmentfault.com/a/1190000038518587

為什麼要有圖?

1、前面我們學習到的線性表與樹

2、線性表侷限一個直接前驅和一個直接後續的關係

3、樹也只能有一個直接前驅、也就是父節點

4、我們需要多對多的關係的時候,就需要使用到圖

一、什麼是圖圖的基本介紹

圖是一種資料結構,其中結點可以具有零個或多個相鄰元素,兩個節點之間的連線稱為邊、結點也可以稱為頂點

圖的常用知識概念圖的常見表達方式

圖的表達方式有兩種: 二維陣列表示(領接矩陣);連結串列表示(鄰接表)

頂點0 ->頂點1、2、3、4、5、時,若能連通則是1,否則0 表示

二、透過示例認識圖圖的快速入門案例

根據如圖所示,使用鄰接矩陣展示連線效果,1 表示能連線 0-表示不能連線

思路分析

1.使用集合的方式儲存節點資訊

2.使用二維陣列[][]儲存矩陣資訊

4.新增兩個節點之間的連線時,需要記錄兩個節點的下標

public class Graph {    private ArrayList<String > vertexList;//儲存頂點的集合    private int[][] edges;//儲存頂點對應圖的鄰接矩陣    private int numOfEdges;//表示邊的數目    //構造器    public Graph(int n ){        edges = new int[n][n];        vertexList = new ArrayList<String>(n);        numOfEdges = 0;    }    //插入節點    public void  insertVertex(String vertex){        vertexList.add(vertex);    }    //新增邊    public void insertEdge(int v1,int v2,int weight){        edges[v1][v2] = weight;        edges[v2][v1] = weight;        numOfEdges++;    }    //返回目前的節點個數    public int getNumOfVertex(){        return vertexList.size();    }    //得到目前邊的個數    public int getNumOfEdges(){        return numOfEdges;    }    //返回下標對應節點資料    public String getValueByIndex(int i){        return vertexList.get(i);    }    //返回兩個節點之間的權值    public int getWeight(int v1,int v2){        return edges[v1][v2];    }    //顯示圖對應的矩陣    public void showGraph(){        for(int[] link :edges){            System.out.println(Arrays.toString(link));        }    }}

接下來我們按照圖所示,將節點:A、B、C、D、E 新增進demo看看

public static void main(String[] args) {     //節點的陣列     String[] arr = {"A","B","C","D","E"};     //建立圖物件     Graph graph = new Graph(arr.length);     //迴圈新增頂點項     for(String data :arr){            graph.insertVertex(data);     }     graph.showGraph();}執行結果如下:[0, 0, 0, 0, 0][0, 0, 0, 0, 0][0, 0, 0, 0, 0][0, 0, 0, 0, 0][0, 0, 0, 0, 0]

根據執行結果來說,我們新增成功了,但是發現了嘛?為什麼全是0?

那是我們沒有新增邊,並且如圖所示是 無向圖 ,現在我們進行新增邊

如圖連線的節點是: A-B/B-A、A-C/C-A、B-C/C-B、B-E/E-B、B-D/D-B

public static void main(String[] args) {    //節點的陣列     String[] arr = {"A","B","C","D","E"};     //建立圖物件     Graph graph = new Graph(arr.length);     //迴圈新增頂點項     for(String data :arr){            graph.insertVertex(data);     }     //graph.showGraph();     //新增邊 //`A-B/B-A、A-C/C-A、B-C/C-B、B-E/E-B、B-D/D-B`        graph.insertEdge(0,1,1);//`A-B     graph.insertEdge(0,2,1);//`A-C     graph.insertEdge(1,2,1);//`B-C     graph.insertEdge(1,4,1);//`B-E     graph.insertEdge(1,3,1);//`B-D     graph.showGraph();}執行結果如下:[0, 1, 1, 0, 0][1, 0, 1, 1, 1][1, 1, 0, 0, 0][0, 1, 0, 0, 0][0, 1, 0, 0, 0]

我們可以對比一下上面的圖,是否正確關聯起來

圖遍歷介紹

所謂的圖遍歷,即是對結點的訪問。

一個圖有那麼多個結點,如何遍歷這些結點?

需要特定策略,一般有兩種訪問策略: (1)深度優先遍歷(2)廣度優先遍歷

三、圖的深度優先遍歷介紹圖的深度優先搜尋(Depth First Search)

1.首先訪問第一個鄰接結點,然後 再以這個被訪問的鄰接結點 作為 初始結點 ,訪問 它的第一個鄰接結點

(每次都在訪問完當前結點後,再以之前訪問的當前結點的第一個鄰接結點)

2.這樣的訪問策略是優先往縱向挖掘深入,而不是對一個結點的所有鄰接結點進行橫向訪問

深度優先遍歷演算法步驟

1.訪問 初始結點v ,並 標記結點v為已訪問2.查詢 結點v 的 第一個鄰接結點w

3.若 w存在 ,則繼續執行4,如果 w不存在 ,則回到第1步,將 從v的下一個結點 繼續

4.若w未被訪問,對w進行深度優先遍歷遞迴(即把 w當做另一個v,然後進行步驟123 )

5.查詢結點v的w鄰接結點的下一個鄰接結點,轉到步驟3

以上面建立的圖,進行一個示例深度優先遍歷的圖解分析,假設初始點:A

引用示例圖解分析演算法步驟

第一步:訪問初始結點v,並標記結點v為已訪問

第二步:查詢結點v的第一個鄰接結點w

第三步:若 w存在 ,則繼續執行,檢查若w是否未被訪問,則對w進行深度優先遍歷遞迴(即把 w當做另一個v,然後進行步驟123 ),如果 w不存在 ,則回到第1步,將 從v的下一個結點 繼續,

第四步:將w節點當做另一個v,執行步驟1-2-3

第五步:C的鄰接節點不存在,返回上一層,即是B節點,從下一個節點繼續

第六步:D的鄰接節點不存在,返回上一層,即是B節點,從下一個節點繼續

深度優先搜尋程式碼實現

1.我們需要記錄某個節點是否被訪問

2.我們查詢節點V的鄰接節點W,需要知道w的下標,所以需要求出w的下標

根據我們前面的二維陣列以及遍歷思路,A的下一鄰接節點B,就是 [A][B] >0

// 得到領接節點的下標public int getFirstNeighbor(int index){    for (int j =0; j<vertexList.size() ; j++){        if(edges[index][j] > 0){            return j;        }    }    return -1;}

3.我們查詢新節點V的鄰接節點W,需要根據前一個鄰接節點的下標獲取

我們需要C的節點鄰接節點W,就需要前一個鄰接節點C的下標

//根據前一個鄰接節點的下標獲取下一個領接節點public int getNextNeighbor(int v1,int v2){    for (int j = v2 +1 ;j<vertexList.size();j++){        if(edges[v1][j] >0){            return j;        }    }    return -1;}

那麼按照圖解思路,我們的演算法方法程式碼(有缺陷,只能訪問一次)就是

public class Graph {    //省略之前關鍵程式碼        private boolean[] isVisited;//記錄某個節點是否被訪問        //構造器    public Graph(int n ){        edges = new int[n][n];        vertexList = new ArrayList<String>(n);        numOfEdges = 0;        isVisited = new boolean[n];    }    //深度優先遍歷方法    public void dfs(boolean [] isVisited,int i ){        //輸出節點進行訪問        System.out.print(getValueByIndex(i) + " - >");        //標記已訪問        isVisited[i] = true;        //查詢當前節點i的鄰接節點w        int w = getFirstNeighbor(i);        while(w != -1){            //鄰接節點w未被訪問            if(!isVisited[w]){                dfs(isVisited,w);            }            //如果w被訪問過了            w = getNextNeighbor(i,w);        }    }    public void dfs(){        for(int j=0; j< getNumOfVertex(); j++){            if(!isVisited[j]){                dfs(isVisited,j);            }        }    }

我們根據之前新增的demo測試遍歷輸出看看

public static void main(String[] args) {    //節點的陣列    String[] arr = {"A","B","C","D","E"};    //建立圖物件    Graph graph = new Graph(arr.length);    //迴圈新增頂點項    for(String data :arr){        graph.insertVertex(data);    }    //graph.showGraph();    //新增邊    //`A-B/B-A、A-C/C-A、B-C/C-B、B-E/E-B、B-D/D-B`    graph.insertEdge(0,1,1);//`A-B    graph.insertEdge(0,2,1);//`A-C    graph.insertEdge(1,2,1);//`B-C    graph.insertEdge(1,4,1);//`B-E    graph.insertEdge(1,3,1);//`B-D    graph.showGraph();    graph.dfs();}執行結果如下:[0, 1, 1, 0, 0][1, 0, 1, 1, 1][1, 1, 0, 0, 0][0, 1, 0, 0, 0][0, 1, 0, 0, 0]A - >B - >C - >D - >E - >
四、圖的廣度優先遍歷介紹圖的廣度優先搜尋(Broad First Search)

1.類似於一個分層搜尋的過程

2.廣度優先遍歷需要使用一個佇列以保持訪問過的結點的順序,以便按這個順序來訪問這些結點的鄰接結點

廣度優先遍歷演算法步驟

1.訪問 初始結點v 並 標記 結點v為 已訪問 。

2.結點 v入佇列

3.當佇列 非空時繼續執行 ,否則 初始結點v的演算法 結束。

4.出佇列,取得 佇列頭結點u 。

5.查詢 結點u 的 第一個鄰接結點w 。

6.若結點u的 鄰接結點w不存在 ,則轉到 步驟3 ;

否則迴圈執行以下三個步驟:

6.1 若結點w尚 未被訪問 ,則訪問結點w並 標記為已訪問 。

6.2 把 結點w入佇列

6.3 接著查詢結點u的 繼w鄰接結點後 的 下一個鄰接結點 ,當做w轉到步驟6。

以上面建立的圖,進行一個示例廣度優先遍歷的圖解分析,假設初始點:A

引用示例圖解分析演算法步驟

第一步:訪問初始結點v,並標記結點v為已訪問,並將節點v入佇列

第二步:此時出佇列,獲取佇列頭結點u,查結點u的第一個鄰接結點w

第三步:如果 w不存在 ,則回到第二步,查詢結點u的繼w的下一個 鄰接節點結點 繼續。若 w存在 ,則檢查w是否未被訪問,未訪問則對w進行標記訪問,並且 入佇列 ,且繼續查詢 繼w鄰接節點後的下一個節點 當做w,接著判斷

第四步:接著查詢結點u的 繼w鄰接結點後 的 下一個鄰接結點 ,當做結點w

第五步:如果 w不存在 ,則回到第二步,查詢結點u的繼w的下一個 鄰接節點結點 繼續。若 w存在 ,則檢查w是否未被訪問,未訪問則對w進行標記訪問,並且 入佇列 ,且繼續查詢 繼w鄰接節點後的下一個節點 當做w,接著判斷

第六步:接著查詢結點u的 繼w鄰接結點後 的 下一個鄰接結點 ,當做結點w

第七步:如果 w不存在 ,則回到第二步,查詢結點u的繼w的下一個 鄰接節點結點 繼續,直至結束回到思路的第二步,代表結點u(A)結束

第八步:出佇列,取得佇列頭結點U、進行查詢鄰接結點w

第九步:如果 w不存在 ,則回到第二步,查詢結點u的繼w的下一個 鄰接節點結點 繼續。若 w存在 ,則檢查w是否未被訪問,未訪問則對w進行標記訪問,並且 入佇列 ,且繼續查詢 繼w鄰接節點後的下一個節點 當做w,接著判斷

第十步:接著查詢結點u的 繼w鄰接結點後 的 下一個鄰接結點 ,當做結點w

第十一步:如果 w不存在 ,則回到第二步,查詢結點u的繼w的下一個 鄰接節點結點 繼續。若 w存在 ,則檢查w是否未被訪問,未訪問則對w進行標記訪問,並且 入佇列 ,且繼續查詢 繼w鄰接節點後的下一個節點 當做w,接著判斷

第十二步:如果 w不存在 ,則回到第二步,查詢結點u的繼w的下一個 鄰接節點結點 繼續,直至結束回到思路的第二步,代表結點u(B)結束

廣度優先搜尋程式碼實現

1.我們可以先一個節點的廣度優先方法,其次其他節點迴圈呼叫即可

//對一個節點進行廣度優先遍歷方法public void bfs(boolean[] isVisited,int i ){    int u;//表示佇列頭節點的下標    int w;//表示鄰接節點的下標    //需要一個佇列記錄訪問的順序    LinkedList queue = new LinkedList();    //訪問節點,輸出節點資訊    System.out.print(getValueByIndex(i) + " ->");    isVisited[i] = true;    //將節點加入佇列,記錄訪問順序    //佇列尾部新增,頭部取    queue.addLast(i);    while (!queue.isEmpty()){        //取出佇列的頭結點,刪掉        u = (Integer) queue.removeFirst();        //查詢頭結點u的鄰接節點        w = getFirstNeighbor(u);        //w != -1 代表找到鄰接節點        while(w!= -1 ){            //判斷是否訪問過            if(!isVisited[w]){                //若沒有訪問過,則輸出並標記已訪問                System.out.print(getValueByIndex(i) + " ->");                isVisited[w] = true;                //將節點入佇列,代表訪問過                queue.addLast(w);            }            //以u為起點,查詢w鄰接結點的下一個鄰接結點            w = getNextNeighbor(u,w);        }    }}

2.接下來則遍歷所有節點進行廣度優先搜尋

//廣度優先遍歷方法public void bfs(){    for(int j=0; j< getNumOfVertex(); j++){        //沒有被訪問過才進行廣度優先搜尋         if(!isVisited[j]){            bfs(isVisited,j);         }    }}

我們根據之前新增的demo測試遍歷輸出看看

public static void main(String[] args) {    //節點的陣列    String[] arr = {"A","B","C","D","E"};    //建立圖物件    Graph graph = new Graph(arr.length);    //迴圈新增頂點項    for(String data :arr){        graph.insertVertex(data);    }    //graph.showGraph();    //新增邊    //`A-B/B-A、A-C/C-A、B-C/C-B、B-E/E-B、B-D/D-B`    graph.insertEdge(0,1,1);//`A-B    graph.insertEdge(0,2,1);//`A-C    graph.insertEdge(1,2,1);//`B-C    graph.insertEdge(1,4,1);//`B-E    graph.insertEdge(1,3,1);//`B-D    graph.showGraph();    //graph.dfs();    graph.bfs();}運算結果如下:[0, 1, 1, 0, 0][1, 0, 1, 1, 1][1, 1, 0, 0, 0][0, 1, 0, 0, 0][0, 1, 0, 0, 0]A ->B ->C ->D ->E ->

作者:28640

出處:https://segmentfault.com/a/1190000038518587

17
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • NodeJS鏈路追蹤與效能最佳化,首殺效能提升50%