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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

ReviewForJob——深度优先搜索的应用

發布時間:2023/12/3 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ReviewForJob——深度优先搜索的应用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

【0】README

1)本文旨在 介紹?ReviewForJob——深度優先搜索的應用 及其 源碼實現 ;

2)搜索樹的技術分為廣度優先搜索 和 深度優先搜索:而廣度優先搜索,我們前面利用 廣度優先搜索計算無權最短路徑已經做過分析了,有興趣的可以參考?廣度優先搜索相關

3)圖經過 深度優先搜索后會生成多個 樹:這種樹叫深度優先樹,要知道多個 深度優先樹就組合成了深度優先森林了;

4)深度優先搜索的應用有:?

4.1)基于無向圖的dfs應用:遍歷無向圖,查找無向圖的割點和背向邊(雙連通性);

4.2)基于有向圖的dfs應用:遍歷有向圖,查找強分支 等, 下面一一對 dfs的 應用荔枝進行分析;

4.3)其中查找無向圖的割點和背向邊 是 核心知識,后面查找強分支會基于 割點和背向邊進行分析;遍歷有向圖和 遍歷無向圖 是 dfs 應用的最基礎的荔枝,且他們的遍歷方式 大致差不多,因為 dfs 就是相當于 對樹的先序遍歷;


【1】深度優先搜索基礎(depth first search——DFS)

1)圖的深度優先搜索遍歷(DFS)類似于二叉樹的先序遍歷。它的基本思想是(steps):?

step1)首先訪問出發點v, 并將其標記為已訪問過;

step2)然后選取與v 鄰接的未被訪問的任意一個頂點w , 并訪問它;

step3)再選取與w 鄰接的未被訪問的任一頂點并訪問它, 依次重復進行;

step4)當一個頂點所有的鄰接頂點都被訪問過時, 則依次退回到最近被訪問過的頂點(這里就是一個遞歸訪問的過程), 若該頂點還有其他鄰接頂點未被訪問, 則從這些未被訪問的頂點中取一個重復上述的訪問過程, 直至圖中所有頂點都被訪問過為止;

2)dfs的應用——遍歷無向圖的荔枝?dfs 遍歷無向圖源碼

// 以下數組的 0 號下標 通通不用。 int visited[VertexNum+1]; // 頂點被訪問狀態 visited[i]==1|0(已訪問|未訪問) int num[VertexNum+1]; // 頂點被訪問的序號. int parent[VertexNum+1]; // parent[i]=j 表明 頂點i 緊跟著頂點j之后 被訪問. int counter=0; // 已被訪問的頂點數量.// dfs 深度優先搜索算法. // 鄰接表adjList(圖的標準表示方法),vertexIndex 頂點索引,depth 用于打印空格,visted數組 用于存儲 頂點訪問狀態. void dfs_undirected_graph(AdjList adjList, int vertex, int depth) // step1: 從vertex 觸發. {int i;Vertex temp = adjList->array[vertex-1];int adjVertex;visited[vertex] = 1; // step1: 更新vertex 頂點為 已訪問狀態.num[vertex] = ++counter; // 頂點vertex 被訪問的序號. while(temp->next) // step2: 遍歷 vertex頂點的 鄰接頂點.{adjVertex = temp->next->index; if(!visited[adjVertex]) // step2: 鄰接頂點沒有被訪問過, 則利用dfs 進行訪問.{parent[adjVertex] = vertex; // just for printing effectfor(i = 0; i < depth; i++) printf(" ");printf("v[%c]->v[%c] (build edge)\n", vertex+64, adjVertex+64); // 都說了是先序遍歷, 記錄函數在 dfs遞歸之前.dfs_undirected_graph(adjList, adjVertex, depth+1); // 遞歸dfs} temp = temp->next;} }



【2】dfs的應用——雙連通性?dfs應用——雙連通性源碼

0)雙連通性: 如果一個連通的無向圖中的任意一個頂點刪除后,剩下的圖仍然是連通的,那么這樣的無向圖就是雙連通的;

1)看幾個定義:

定義1)背向邊(backside): 當我們處理(v, w)時,發現 w 已被標記,并且當我們處理 (w, v) 時 發現v 已被標記,那么我們就畫一條虛線,并稱之為 背向邊;

定義2)割點(articulation):如果一個圖不是雙連通的,那么將其刪除后將不在連通的那些頂點叫做割點;下面的圖片中C 和 D 就是割點

2)如何尋找背向邊? 這可是個大活:

你要想1)我們看看 (D, A) 和 (F, D) 為什么是背向邊? 看看 dfs 的遍歷順序,A->B->C->D, 遍歷到D后,D開始遍歷它的鄰接頂點,D會遍歷A 發現 A已經被訪問過了; 之后當 dfs 遞歸算法 回溯到 頂點A 的時候,A 遍歷鄰接頂點D ,發現D被 訪問過了,所以可以確認(D,A)就是背向邊;為什么這么說,下面給出代碼的調試信息:

// 下列數組 0 號下標通通不用. int visited[VertexNum+1]; // 頂點被訪問狀態 visited[i]==1|0(已訪問|未訪問) int num[VertexNum+1]; // 頂點被訪問的序號. int parent[VertexNum+1]; // parent[i]=j 表明 頂點i 緊跟著頂點j之后 被訪問. int low[VertexNum+1]; // low[i]=j 表明 頂點i 可以訪問(包括通過背向邊)的最先被訪問的頂點是頂點j. int counter=0; // 已被訪問的頂點數量.// dfs 深度優先搜索算法.用于尋找背向邊和割點. // 鄰接表adjList(圖的標準表示方法),vertexIndex 頂點索引,depth 用于打印空格 void dfs_find_articulation(AdjList adjList, int vertex, int depth) {int i;Vertex temp = adjList->array[vertex-1];int adjVertex;visited[vertex] = 1; // 更新vertex 頂點為 已訪問狀態. vertex 從1開始取,所以減1.low[vertex] = num[vertex] = ++counter; // 頂點vertex 被訪問的序號. while(temp->next){adjVertex = temp->next->index; if(!visited[adjVertex]) // 鄰接頂點沒有被訪問過, 則利用dfs 進行訪問.{parent[adjVertex] = vertex; // just for printing effectfor(i = 0; i < depth; i++) printf(" ");printf("v[%c]->v[%c] (build edge) (%c, %d/%d)\n", vertex+64, adjVertex+64, vertex+64, num[vertex], low[vertex]);dfs_find_articulation(adjList, adjVertex, depth+1);if(low[adjVertex] >= num[vertex]) // 判斷 vertex是否是割點.{// just for printing effectfor(i = 0; i < depth; i++) printf(" ");printf("%c is an articulation point for (low[%c]=%d) >= (num[%c]=%d)\n", vertex+64, adjVertex+64, low[adjVertex], vertex+64, num[vertex]);}low[vertex] = min(low[vertex], low[adjVertex]); // 基于后序遍歷更新low[].} else // 鄰接頂點被訪問過.{ if(parent[vertex] != adjVertex) // true,則(vertex, adjVertex)就是背向邊{low[vertex] = min(low[vertex], num[adjVertex]); // 更新 當前節點i的low[i] // just for printing effect, 不作為算法的一部分.for(i = 0; i < depth; i++) printf(" "); // 不作為算法的一部分over.printf("v[%c]->v[%c] (backside) (%c, %d/%d)\n", vertex+64, adjVertex+64, vertex+64, num[vertex], low[vertex]); } } temp = temp->next;} }


對上面的源碼和打印結果圖的分析(Analysis)

A1)從結果圖中看出, 背向邊有4條,錯誤!其實只有2條。我們只看(D,A)這條。?當 D 遍歷A的時候,產生背向邊,而當 A遍歷D的時候產生背向邊;但它們是同一條邊;那如何 記錄 背向邊呢? 要知道 背向邊的起點是后訪問的節點,終點是先訪問的節點,也就是說 當 num[vertex] > num[adjVertex] (vertex, adjVertex) 才是背向邊;背向,背向,你就當它是逆向的意思就行啦!

A2)為什么41行的 條件語句就可以判斷 (vertex,adjVertex)就是背向邊? 要知道 parent數組記錄的是 上一個訪問的頂點,如 parent[i]=j 則表示 在迭代i 之前訪問的頂點是 頂點j。 因為 如果 頂點 adjVertex 恰好先于 頂點vertex 被訪問的話,第21行的代碼不滿足,程序跳轉到41行執行。如果沒有 41行的條件語句做判斷,就會認為 相鄰兩個被訪問的頂點(num[i] - num[j] ==1)組成的邊(i,j)是 背向邊,這樣顯然是不行。(因為相鄰被訪問的頂點不可能是背向邊,參見上面的深度優先樹),那所以要排除這種case,加上了if 條件語句;

2)分析low數組: low[i]=j 表明 low[i]記錄的是 當前頂點i 可以訪問(包括通過背向邊)的最先被訪問的頂點是頂點j;看上面 的深度優先樹,(D,A)是背向邊,low[D]=1,因為它可以訪問到 A,而num[A]=1;同時你也看到了 low[C]=1 也等于1,因為后序遍歷更新了 其 low值。什么叫后序遍歷? 后序的意思就是 更新動作在 dfs 遞歸函數之后執行;參見37行;

3)如何更新low? 要知道 low[v] 取值有三種:

case1)num[v]:第17行代碼

case2)所有背向邊(v,w) 中的最低num(w); 第43行代碼;通過背向邊可以訪問的頂點的num最小值(越先被訪問,值就越小);

case3)樹的所有邊(v, w) 中的最低 low(w);第37行代碼;這里就是一個 dfs 回溯的 更新 low值了,比如 C 訪問D, D 訪問A,D的low值會更新為1,當D 被訪問完后,會回溯到C,由于第37行的更新low值是后序,所以 low[C] 會 和 low[D] 做比較并選取最小值,同時dfs繼續回溯到B.......;就是這個樣子的;(開心)

4)如何查找割點?

4.1)割點肯定有大于等于2個的鄰接頂點;

4.2)某個頂點是割點當且僅當它有某個兒子 w 使得 low(w) >= num(v);參見頂點C 和 頂點D;第30行代碼用于判斷割點;


【3】dfs應用——遍歷有向圖?dfs 應用遍歷有向圖源碼

1)intro:利用dfs 遍歷有向圖,其idea 同 dfs 遍歷無向圖相同;大致的steps 如下:

step1)選取任意一個未被訪問的頂點為起點,進行dfs;

step2)待dfs 執行完后,可能還有一些節點無法通過一次dfs 遍歷到的(因為圖可能是非強連通的),這時就需要選取任意一個未被訪問的頂點作為起點,繼續dfs,返回step1;

// 使用 dfs 遍歷有向圖.printf("\n=== dfs_find_directed_graph(adjList, 1, 1) ===\n");dfs_find_directed_graph(adjList, 2, 1); // step1:for(i=1; i<=VertexNum; i++) // step2:{if(!visited[i]){printf("\n");dfs_find_directed_graph(adjList, i, 1);}}

2)基于dfs 遍歷有向圖的源碼如下:(與遍歷無向圖差不了多少)

#define VertexNum 10int min(int a, int b);// 0 號下標通通不用. int visited[VertexNum+1]; // 頂點被訪問狀態 visited[i]==1|0(已訪問|未訪問) int num[VertexNum+1]; // 頂點被訪問的序號. int parent[VertexNum+1]; // parent[i]=j 表明 頂點i 緊跟著頂點j之后 被訪問. int low[VertexNum+1]; // low[i]=j 表明 頂點i 可以訪問(包括通過背向邊)的最先被訪問的頂點是頂點j. int counter=0; // 已被訪問的頂點數量.// dfs 深度優先搜索算法. 遍歷有向圖. // 鄰接表adjList(圖的標準表示方法),vertexIndex 頂點索引,depth 用于打印空格 void dfs_find_directed_graph(AdjList adjList, int vertex, int depth) {int i;Vertex temp = adjList->array[vertex-1];int adjVertex;visited[vertex] = 1; // 更新vertex 頂點為 已訪問狀態. vertex 從1開始取,所以減1.low[vertex] = num[vertex] = ++counter; // 頂點vertex 被訪問的序號. while(temp->next){adjVertex = temp->next->index; if(!visited[adjVertex]) // 鄰接頂點沒有被訪問過, 則利用dfs 進行訪問.{parent[adjVertex] = vertex; // just for printing effectfor(i = 0; i < depth; i++) printf(" ");printf("v[%c]->v[%c] (build edge) \n", vertex+64, adjVertex+64);dfs_find_directed_graph(adjList, adjVertex, depth+1);low[vertex] = min(low[vertex], low[adjVertex]); // 基于后序遍歷更新low[].} else // if(visited[adjVertex-1]) 鄰接頂點被訪問過.{ if(parent[vertex] != adjVertex) // true,則(vertex, adjVertex)就是背向邊{low[vertex] = min(low[vertex], num[adjVertex]); // 更新 當前節點i的low[i]// just for printing effect, 不作為算法的一部分.for(i = 0; i < depth; i++) printf(" "); printf("v[%c]->v[%c] (backside)\n", vertex+64, adjVertex+64);// 不作為算法的一部分over.} } temp = temp->next;} } int min(int a, int b) {return a>b? b:a; }


補充)深度優先搜索的一種用途是: 檢測一個 有向圖是否是無圈圖。法則如下:一個有向圖是無圈圖當且僅當它沒有背向邊;(這個法則,本文的章節【2】有敘述);ps: 拓撲排序也可以用來確定一個圖是否是無圈圖;


【4】dfs應用——查找強分支?dfs應用——查找強分支源碼

1)intro:通過執行兩次深度優先搜索,我們可以檢測一個有向圖是否是強連通的,如果它不是強連通的,那么可以得到頂點的一些子集(強連通子圖 或 強分支);

2)由于要選取 最大的 num[i], 所以用到了二叉堆優先隊列,首先將 num[] 數組元素插入大根堆,然后再 deleteMin() 刪除最小元素 選取 最大的 num[i];

3)基于 dfs 查找強分支 的 算法描述

step1)在有向圖G 上執行 dfs 形成深度優先生成森林,通過對 深度優先生成森林的后序遍歷將 G中頂點的訪問順序編號(用 num[] 數組記錄其訪問序號);

// 使用 dfs 查找強連通分支.// step1: 基于dfs 遍歷 有向圖G,對頂點的訪問順序編號.printf("\n=== dfs_find_directed_graph(adjList, 2, 1) ===\n");dfs_find_strong_component(adjList, 2, 1); // start=1.for(i=1; i<=VertexNum; i++){if(!visited[i]){printf("\n");dfs_find_strong_component(adjList, i, 1);}}// step1 over.

step2)把G的所有邊反向,形成 Gr(reverse);

//step2: 把G的所有邊反向 -> Gr.adjListReverse = init(capacity); if(adjListReverse==NULL){return;} printf("\n\t === build reverse adjacency list ===\n");for(i=0;i<row;i++){ for(j=0; j<col; j++){if(adjArray[i][j]){insertAdjList(adjListReverse, adjArray[i][j]-1, i+1, adjArray[i][j]); // 插入節點到鄰接表.(無向圖權值為全1)}}} // step2 over.

step3)從編號最大的頂點開始,依次進行基于 dfs 的 有向圖遍歷,每次遍歷的起點都作為 強連通子圖(強分支)的根;

// step3: 從序號最大的頂點開始,依次對Gr 進行 dfs.(這里需要建立一個大根堆) // step3.1: 利用num[] 建立大根堆heap = initBinaryHeap(VertexNum+1); // 因為0號下標不用.for(i=1; i<=VertexNum; i++){insert(createHeapNode(i, num[i]), heap);// 初始化數組為0visited[i] = 0;num[i] = 0; parent[i] = 0; } // step3.1 over.printf("\n\t === binary heap is as follows.===");printBinaryHeap(heap);counter=0; // 初始化count=0;// step3.2 依次選取 最大序號的頂點進行dfsprintf("\n=== dfs_find_strong_component(adjListReverse, deleteMin(heap).index, 1) ===\n");while(!isEmpty(heap) && counter!=VertexNum){ vertex = deleteMin(heap).index; // 依次選取 最大訪問序號的頂點. if(!visited[vertex]) // 如果該頂點沒有被訪問的話.{dfs_find_strong_component(adjListReverse, vertex, 1);} }

step4)在該深度優先生成森林中的每棵樹 都形成一個強連通分支;

4)源碼如下

#define VertexNum 10int min(int a, int b);// 0 號下標通通不用. int visited[VertexNum+1]; // 頂點被訪問狀態 visited[i]==1|0(已訪問|未訪問) int num[VertexNum+1]; // 頂點被訪問的序號. int parent[VertexNum+1]; // parent[i]=j 表明 頂點i 緊跟著頂點j之后 被訪問. int counter=0; // 已被訪問的頂點數量.// dfs 深度優先搜索算法. // 鄰接表adjList(圖的標準表示方法) , vertex 頂點索引,depth 用于打印空格 void dfs_find_strong_component(AdjList adjList, int vertex, int depth) {int i;Vertex temp = adjList->array[vertex-1];int adjVertex;visited[vertex] = 1; // 更新vertex 頂點為 已訪問狀態. vertex 從1開始取,所以減1. while(temp->next){adjVertex = temp->next->index; if(!visited[adjVertex]) // 鄰接頂點沒有被訪問過, 則利用dfs 進行訪問.{ parent[adjVertex] = vertex; // just for printing effectfor(i = 0; i < depth; i++) printf(" ");printf("v[%c]->v[%c] (build edge) \n", vertex+64, adjVertex+64);dfs_find_strong_component(adjList, adjVertex, depth+1); } else // if(visited[adjVertex-1]) 鄰接頂點被訪問過.{ if(parent[vertex] != adjVertex) // true,則(vertex, adjVertex)就是背向邊{ // just for printing effect, 不作為算法的一部分.for(i = 0; i < depth; i++) printf(" "); printf("v[%c]->v[%c] (backside)\n", vertex+64, adjVertex+64);// 不作為算法的一部分over.} } temp = temp->next;} num[vertex] = ++counter; // 頂點vertex 被訪問的序號. // attention, p249: 明確說明 使用dfs 后序遍歷設置 頂點編號. } int min(int a, int b) {return a>b? b:a; }

對以上代碼的分析)?注意,step1中明確說了后序遍歷記錄訪問序號,參見 第 45 行代碼;


測試用例如下:

void main() {int capacity=VertexNum; // 頂點個數AdjList adjList; // 鄰接表AdjList adjListReverse; // 反向鄰接表BinaryHeap heap; // 大根堆, 用于依次選取 序號最大的邊. 堆節點類型是結構體, 因為要存儲頂點編號和對應的被訪問序號.int row=VertexNum, col=3, i, j; int vertex;int adjArray[VertexNum][VertexNum] = {{2, 4, 0}, // A{3, 6, 0}, // B{1, 4, 5}, // C{5, 0, 0}, // D{0, 0, 0}, // E{3, 0, 0}, // F{6, 8, 0}, // G{6, 10, 0}, // H{8, 0, 0}, // I{9, 0, 0}, // J};// init adjacency list.adjList = init(capacity); if(adjList==NULL){return;} printf("\n\n\t === reviww for DFS applie into directed graph ===");printf("\n\t === build adjacency list ===\n"); for(i=0;i<row;i++){ for(j=0; j<col; j++){if(adjArray[i][j]){insertAdjList(adjList, i, adjArray[i][j], adjArray[i][j]); // 插入節點到鄰接表.(無向圖權值為全1)}}}printAdjList(adjList);// 使用 dfs 查找強連通分支.// step1: 基于dfs 遍歷 有向圖G,對頂點的訪問順序編號.printf("\n=== dfs_find_directed_graph(adjList, 2, 1) ===\n");dfs_find_strong_component(adjList, 2, 1); // start=1.for(i=1; i<=VertexNum; i++){if(!visited[i]){printf("\n");dfs_find_strong_component(adjList, i, 1);}}// step1 over.printf("\n\t === num array are as follows. ===");printArray(num, VertexNum+1); //step2: 把G的所有邊反向 -> Gr.adjListReverse = init(capacity); if(adjListReverse==NULL){return;} printf("\n\t === build reverse adjacency list ===\n");for(i=0;i<row;i++){ for(j=0; j<col; j++){if(adjArray[i][j]){insertAdjList(adjListReverse, adjArray[i][j]-1, i+1, adjArray[i][j]); // 插入節點到鄰接表.(無向圖權值為全1)}}} // step2 over.printAdjList(adjListReverse); // step3: 從序號最大的頂點開始,依次對Gr 進行 dfs.(這里需要建立一個大根堆) // step3.1: 利用num[] 建立大根堆heap = initBinaryHeap(VertexNum+1); // 因為0號下標不用.for(i=1; i<=VertexNum; i++){insert(createHeapNode(i, num[i]), heap);// 初始化數組為0visited[i] = 0;num[i] = 0; parent[i] = 0; } // step3.1 over.printf("\n\t === binary heap is as follows.===");printBinaryHeap(heap);counter=0; // 初始化count=0;// step3.2 依次選取 最大序號的頂點進行dfsprintf("\n=== dfs_find_strong_component(adjListReverse, deleteMin(heap).index, 1) ===\n");while(!isEmpty(heap) && counter!=VertexNum){ vertex = deleteMin(heap).index; // 依次選取 最大訪問序號的頂點. if(!visited[vertex]) // 如果該頂點沒有被訪問的話.{dfs_find_strong_component(adjListReverse, vertex, 1);} } }



對上面打印結果的分析)如何選取強連通分支呢?(參看深度遍歷Gr)

Attention)backside 表示 背向邊,而 build edge 表示建立邊;顯然 背向邊的起點和終點屬于不同的 強連通分支;而build edge 表示其起點和終點屬于同一個強連通分支;

v[H]->v[G] (backside) // 本次dfs遍歷 形成 兩個強連通分支 {H} {G}v[H]->v[I] (build edge) // 本次 dfs 遍歷 形成 強連通分支 {H, I, J},結合上一行的結果,則最終的結果是 {H, I, J} {G}v[I]->v[J] (build edge)v[J]->v[H] (backside)v[B]->v[A] (build edge) // 本次遍歷dfs 遍歷形成 強連通分支 {B, A, C, F}v[A]->v[C] (build edge)v[C]->v[B] (backside)v[C]->v[F] (build edge)v[F]->v[B] (backside)v[F]->v[G] (backside)v[F]->v[H] (backside)v[D]->v[A] (backside) // 本次dfs遍歷 形成 {D} {A},又 A 已經被訪問了 所以 只有 {D}v[D]->v[C] (backside) // 本次dfs遍歷 形成 {D} {C} 他們都被訪問過了,都不作為 新的強分支.v[E]->v[C] (backside) // 本次 dfs 遍形成 {E} {C} 由于 {C} 已經被訪問過了,所以形成 {E} 強分支v[E]->v[D] (backside) // 本次 dfs 遍歷形成 {E} {D} 由于他們都被訪問了,所以 沒有強分支綜上所述: 最終的強連通分支有:{G} {B A C F} {D} ?{H I J} ?{E}


創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的ReviewForJob——深度优先搜索的应用的全部內容,希望文章能夠幫你解決所遇到的問題。

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