圖的 DFS 與 BFS 複雜度分析
阿新 • • 發佈:2021-02-09
DFS的複雜度分析:
對於鄰接表的儲存方式:因為鄰接表中每條連結串列上的從第2個結點到表尾結點一定是表頭結點的鄰接點,所以遍歷表頭結點的鄰接的過程中只需要遍歷這些頂點即可,無需遍歷其他的頂點,所以遍歷某個頂點的所有鄰接點的複雜度為O(ei), ei為每個頂點的鄰接點個數, 也就是每條連結串列的邊數。 所以鄰接表版的 dfs 遍歷所有鄰接點的時間複雜度為 O(e1 + e2 + e3 + .... + en) ,因為所有邊數之和為 E , 所以時間複雜度為 O(E) , 又因為訪問每個頂點都必須被訪問一次, 比如設定vis[i] = true, 這個操作一共要執行 V 次,所以,設定所有頂點為已訪問的時間複雜度為O(V), 所以總的時間為查詢所有鄰接點的時間加上設定每個頂點為已訪問的時間,總的時間為 O(E) + O(V) = O(E + V)。 鄰接表的 dfs 遞迴的最大深度就是 連結串列條數。最大深度的情況發生在 遍歷第一條連結串列的第一個鄰接結點時,發現它還未被訪問過,所以遞迴訪問它,而遞迴訪問它的第一個鄰接點又發現它還沒被訪問過,又會去遞迴訪問這個鄰接點,這樣,每次遞迴開始查詢到的都是未訪問過的結點,每次都會進行向下遞迴,每次遞迴層數都加一,這樣遞迴層數就為連結串列條數。鄰接矩陣與鄰接表遍歷過程不同在於,對於鄰接矩陣來說查詢某個頂點的所有鄰接點的過程必須遍歷所有頂點,無論是否鄰接都必須判斷一次。所以查詢每個頂點的鄰接點的時間複雜度都為O(n), 共有 n 個頂點,這 n 個頂點就表現為遞迴深度最大為 n, 所以總的時間複雜度為 O(n + n + ... + n) = O(n ^2), 而設定每個頂點為已訪問的時間複雜度為O(n), 所以總的時間複雜度為O((n^2 + n), 忽略小階複雜度,保留大階複雜度,所以我們通常說它的時間複雜度為 O(n^2)。 鄰接矩陣版的 dfs 遞迴的最大深度是 頂點個數,情況發生的場景和鄰接表最大深度的情況類似,每次遞迴查詢到的第一個鄰接點都是未訪問過的,這樣每次都會 進行向下遞迴,每次遞迴層數都加一,最多遞迴 n 層。void dfs(u){ vis[u] = true; for(遍歷頂點u的所有鄰接點,即遍歷u為表頭的那條連結串列){ // 每輪時間複雜度為 O(ei) if(該鄰接點未被訪問){ dfs(v) } } }
void dfs(u){ vis[u] = true; // 設定 u 頂點為已訪問 for(int v = 0; v < n; v++){ // 每輪的時間複雜度為 O(n) if(vis[v] == false && G[u][v] != inf){ dfs(v) } } }
BFS的複雜度分析
有了上面 DFS 的複雜度分析,BFS 複雜度分析就很簡單了, BFS是一種借用佇列來儲存的過程,分層查詢,優先考慮距離出發點近的點。無論是在鄰接表還是鄰接矩陣中儲存,都需要藉助一個輔助佇列,v個頂點均需入隊,最壞的情況下,空間複雜度為O(v)。 鄰接表形式儲存時,每個頂點均需搜尋一次,每次結點被入隊一次,出隊一次,所以時間複雜度是 O(V),但是遍歷某個頂點所有鄰接點的複雜度是 O(ei), ei 為第 i 條連結串列的弧的條數,也就是鄰接點個數,所以查詢所有鄰接點的複雜度為 O(e1 + e2 + ... + en) = O(E), 加上對每個結點進行入隊出隊的複雜度 O(V), 所以總的時間複雜度為O(E + V).鄰接矩陣儲存方式時,查詢每個頂點的鄰接點所需時間為O(V),即該節點所在的該行的所有列。又因為有n個頂點, 查詢所有鄰接點的時間複雜度為O(V^2), 加上對每個結點進行入隊出隊的複雜度 O(V), 所以總的時間複雜度為O(V^2 + V)。省略低階複雜度,最終複雜度為 O(V^2).void bfs(int s){ // s 為選定的遍歷圖的起點 Queue<int> queue; // 定義一個佇列 queue.push(s); while(!queue.empty()){ int top = queue.top(); // 出隊隊首元素 queue.pop(); for(訪問 top 的所有鄰接點){ // 每輪的時間複雜度為 O(ei) if(如果該鄰接點未曾入隊){ 將該結點入隊; } } } }
void bfs(int s){ // s 為選定的遍歷圖的起點
Queue<int> queue; // 定義一個佇列
queue.push(s);
while(!queue.empty()){
int top = queue.top(); // 出隊隊首元素
queue.pop();
for(訪問 top 的所有鄰接點){ // 每輪的時間複雜度為 O(n)
if(如果該鄰接點未曾入隊){
將該結點入隊;
}
}
}
}
可以看到 DFS 和 BFS 的遍歷方式,每個版本的時間複雜度是相同的。