日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

数据结构与算法--图论-深度优先搜索及其应用

發布時間:2023/12/4 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据结构与算法--图论-深度优先搜索及其应用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

深度優先搜索

  • 深度優先搜索(depth-first search) 是對先序遍歷(preorder traversal)的推廣,我們從某個頂點v開始處理v,然后遞歸的遍歷所有與v鄰接頂點。如果這個過程是對一棵樹進行,那么,由于E=O(V),因此樹的所有頂點在總時間O(E)內都會被訪問到。
  • 方法:
    • 當訪問頂點v時候,我們標記該節點以及訪問過,并且對于未被標記的所有鄰接頂點遞歸調用深度優先搜索,
    • 加上我們對無向圖每條邊(v,w)在鄰接表中出現兩次:一次(v,w),另外一次(w,v)
    • 如下圖過程中執行一次深度優先搜索,什么都沒有操作的一次遍歷,只是深度優先搜索的案例:
/*** @author liaojiamin* @Date:Created in 16:29 2021/1/8*/ public class GraphDepthFirstSearch {//遞歸調用深度優先搜索實現public void depthFirst(Vertex v){v.setKnow(true);for (Vertex vertex : v.getVertexList()) {if(!vertex.isKnow()){depthFirst(vertex);}}} }
  • 以上算法中,對每個頂點的known都是初始化為false,通過只對未訪問過的節點進行進行遞歸調用,我們以此保證不進入無線循環。
  • 如果圖是無向的且不連通的,或者是有向的非強連通的(上一節中有對應說明)不能保證所有節點都能范文到,比如我們的v節點是從中途選取的一個節點,并不是圖的入口節點。
  • 次數我們搜索一個未被標記的節點,然后以這個節點為入口節點進行深度優先搜索,繼續執行這兩個步驟直到所有節點都被標記為止

無向圖

  • 當我們從無向圖的任何一個節點開始深度優先搜索的時候能訪問到圖中的所有節點,那么這個無向圖是連通無向圖。
  • 假設我們所處理的圖都是連通的,如果不連通我們可以找出所有連通分支并將我們的算法一次運用到每個分支上即可(同上步驟)
  • 作為深度優先搜索的案例,在下圖中,我們從A點開始。用以下遍歷流程:
    • 從A節點出發,A已經被訪問過,標記A為true,并遞歸調用dfs(B)
    • dfs(B)標記B節點為true并且遞歸調用dfs(C)
    • dfs(C)標記C節點為true并且遞歸調用dfs(D)
    • dfs(D)遇到A,B但是A與B都已經被標記為true,因此沒有遞歸調用可以進行,此時D-A,D-B為非必要的路徑
    • dfs(D)同時也是鄰接C的頂點,但是C也是被標記為true,因此這里也沒有遞歸調用,此時D-C也是非必要路徑
    • 于是按照遞歸dfs(D)返回到dfs(C)
    • dfs(C)看到B是鄰接節點,但是B已經是true,并且E也是C的鄰接節點,因此調用dfs(E)。
    • dfs(E)將E標記為true,鄰接節點是A,C,都是true,因此忽略A,C,所以E-A,E-C是非必要路徑,因為C-E已經是被范問過的已經存在的路徑,所以剩下E-A是非必要路徑,繼續返回上一層dfs(C)
    • dfs(C)返回dfs(B),dfs(B)忽略A,D,同理BA是已知路徑,上面已經走過,所以BD是非必要路徑
    • 返回上一層dfs(A)忽略D,E,兩個都是非必要路徑
    • 按以上算法遞歸得到,我們實際上對每個表都范問了兩次,一次作為邊(v,w),一次作為邊(w,v),這實際上是每個鄰接表遍歷一次而已
深度優先生成樹
  • 我們用深度優先生成樹(depth-first spanning tree)一圖形的方式來標識上面的遞歸步驟,樹的根節點是入口節點A,第一個被訪問到的頂點,圖中每一條表(v,w)都出現在樹上
    • 如果我們處理(v,w)的時候發現w是為未被標記,或者處理(w,v)的時候發現v是未被標記,那么我們就用樹的一條邊來標識他
    • 如果我們處理(v,w)時候發現w已經被標記,并且處理(w,v)時候發現v已經有標記,那么我們就用虛線稱為背向邊(上述步驟中的非必要路徑),標識這條邊實際上不是這棵樹的一部分,也就是我們去掉這些邊也可以全部遍歷所有節點。
    • 如下圖

  • 如果圖不是連通圖,我們上面討論過,需要多次執行這個算法,直到所有節點都已經被訪問,每次都按照算法生成一顆樹,整個集合就是深度優先生成森林(depth-first spanning forest)
雙連通性
  • 一個連通的無向圖如果不存在被刪除之后使得剩下的圖有不在連通的頂點,那么這樣的無向連通圖就稱為雙連通(biconnected)的。上面案例中的圖是雙連通的。
  • 案例: 如果節點標識計算機,邊是鏈路,如果一臺計算機宕機,則網絡是不受影響的,當然這臺壞的計算機除外。
  • 如果存在一個圖不是雙連通的,那么將其刪除后使得圖不在連通的那些頂點叫做割點(articulation point)。這些節點在許多應用中很重要。下圖中所示不是雙連通的:頂點C,D都是割點。刪除頂點C,使的G不在連通,刪除D使得E,F單獨分離開
  • 深度優先所搜提供一種找出連通圖中的所有割點的線性時間算法:
    • 首先從圖中任意一頂點開始,執行深度優先搜索,并在頂點被訪問的時候編號。對每個頂點v,我們稱為先序編號為Num(v)。然后對于深度優先搜索生成樹
    • 然后對于深度優先搜索生成樹上的每個頂點v,計算編號最低的頂點,也就是在鄰接表中每個節點的鄰接節點中Num(v)的最小值,我們稱為Low(v),該節點可以從v開始通過樹的0條邊(節點本身),或者多條表且有一條背向邊而達到
    • 如下圖中,依據深度優先搜索樹首先得出先序編號,然后支出在上述方法下面可以達到的最低的編號。

  • 如上圖,如A 頂點數據(1/1),第一個數據表示Num(v),是先序遍歷的順序字段,圖中樹結構的先序遍歷順序是A,B,C,D,E,F,G。

  • 第二個數據表示的是Low(v),所有頂點的Low(v)開始都等于Num(v),頂點A的low(v)是本身,

  • D的鄰接節點中存在A節點,所以按以上規則Low(v)是最小的Num(v)所以取A 頂點的Num(v)

  • 同理C節點鄰接節點是D也是1,B節點鄰接C也是1

  • E節點鄰接F,F背向邊到D,所以F,E,都是D節點的Num(v) = 4.

  • G沒有背向節點,所以是本身 7

  • 我們依據以上Low定義可以指定Low(v)是滿足如下規則:

    • 初始值Num(v)
    • 如果有背向邊的話,取所有背向邊(v,w)中最低的Num(w)
    • 取樹的所有邊(v,w)中最低的Low(w)中的最小者
  • 以上規則中第一條是不選取邊,本身就是最小,第二條規則是不選取樹的邊而是選取一條背向邊,第三種規則我們可以用遞歸調用簡單的描述,由于我們需要對v的所有子節點算出Low值后才能得到最小Low(v),因此這是一個后續遍歷。對于任意的(v,w),只要檢查Num(v)和Num(w)就知道他是樹的一條邊還是一條背向邊,(因為按樹生成算法背向邊總數從大的 num值 到小的num值)。因此Low(v)容易計算,我們只需要掃描v的鄰接節點,然后記住最小值。時間復雜度在O(E+V)。

  • 接著我們需要做的就是利用以上信息找到所有割點。

    • 根是割點的時候只有在根節點有大于一個兒子節點的時候成立,如果有2個兒子節點,刪除根則使得不同子樹上節點不連通
    • 對于任何其他頂點v,他是割點的蟲咬條件是,當他的某個兒子節點w使得Low(w) >= Num(v),滿足
    • 還是上圖中的案例,C和D是割點,D有一個兒子節點E,且Lov(E) >= Num(D),兩個都是4
    • 同理C也是割點Low(G) >= Num?

算法實現

  • 最后我們給出以下代碼實現該算法,我們需要在之前的Vertex圖節點中修改幾個屬性,num, low,沿用之前的known,path(父節點),用類變了counter做統計為先序遍歷num編號,如下:
/*** @author liaojiamin* @Date:Created in 16:29 2021/1/8*/ public class GraphDepthFirstSearch {//假設圖基本數據結構已經被讀入鄰接表中private static final List<Vertex_v1> vertices = new ArrayList<>();private Integer counter = 0;/*** 深度優先遞歸模板* */public void depthFirst(Vertex_v1 v){v.setKnow(true);for (Vertex_v1 vertex : v.getVertexList()) {if(!vertex.isKnow()){depthFirst(vertex);}}}/*** 深度優先遞歸賦值nnum* */public void assignNum(Vertex_v1 v){v.setNum(counter++);v.setKnow(true);for (Vertex_v1 vertex_v1 : v.getVertexList()) {if(!vertex_v1.isKnow()){vertex_v1.setPath(v);assignNum(vertex_v1);}}}/*** 同樣的算法,深度優先對Low賦值* */public void assignLow(Vertex_v1 v){v.setLow(v.getNum());for (Vertex_v1 w : v.getVertexList()) {//w > v 標識w是v的子節點if(w.getNum() > v.getNum()){assignLow(w);//割點規則if(w.getLow() > v.getNum()){System.out.println(v.getNum() + " is an articulation point");}v.setLow(Math.min(v.getLow(), w.getLow()));}else if(v.getPath().compareTo(w) != 0){//此處不能寫成: v.setLow(Math.min(v.getLow(), w.getLow()));因為此種情況是在最后二次訪問邊路徑的時候得到,可能w的Low已經被修改成更小的值v.setLow(Math.min(v.getLow(), w.getNum()));}}}/*** 還是用深度優先搜索,結合以上兩種規則,值進行一次遍歷得到* */public void findArt(Vertex_v1 v){v.setKnow(true);v.setNum(counter++);v.setLow(v.getNum());for (Vertex_v1 w : v.getVertexList()) {if(!w.isKnow()){w.setPath(v);findArt(w);if(w.getLow() >= v.getNum()){System.out.println(v.getNum() + " is an articulation point");}v.setLow(Math.min(v.getLow(), w.getLow()));}else if(v.getPath().compareTo(w) != 0){//此處不能寫成: v.setLow(Math.min(v.getLow(), w.getLow()));因為此種情況是在最后二次范文邊路徑的時候得到,可能w的Low已經被修改成更小的值v.setLow(Math.min(v.getLow(), w.getNum()));}}}}
  • 以上算法中assignNum通過執行一次先序遍歷計算Num,然后在后續遍歷計算Low,第三次遍歷可以用來檢查哪些頂點滿足割點的標準,但是執行了三次遍歷比較浪費,我們將兩個方法合成為findArt,在同一個遞歸中完成,得出算法。

上一篇:數據結構與算法–圖論-最短路徑算法應用
下一篇:數據結構與算法–貪婪算法:模擬調度問題

總結

以上是生活随笔為你收集整理的数据结构与算法--图论-深度优先搜索及其应用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。