图的 DFS 与 BFS 复杂度分析
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 递归的最大深度就是 链表条数。最大深度的情况发生在 遍历第一条链表的第一个邻接结点时,发现它还未被访问过,所以递归访问它,而递归访问它的第一个邻接点又发现它还没被访问过,又会去递归访问这个邻接点,这样,每次递归开始查找到的都是未访问过的结点,每次都会进行向下递归,每次递归层数都加一,这样递归层数就为链表条数。
void dfs(u){ vis[u] = true; for(遍历顶点u的所有邻接点,即遍历u为表头的那条链表){ // 每轮时间复杂度为 O(ei) if(该邻接点未被访问){ dfs(v) } } }
邻接矩阵与邻接表遍历过程不同在于,对于邻接矩阵来说查找某个顶点的所有邻接点的过程必须遍历所有顶点,无论是否邻接都必须判断一次。所以查找每个顶点的邻接点的时间复杂度都为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; // 设置 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).
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(如果该邻接点未曾入队){ 将该结点入队; } } } }
邻接矩阵存储方式时,查找每个顶点的邻接点所需时间为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(n) if(如果该邻接点未曾入队){ 将该结点入队; } } } }
可以看到 DFS 和 BFS 的遍历方式,每个版本的时间复杂度是相同的。
这里再附上严蔚敏的教材上对 DFS 和 BFS 的复杂度分析:
DFS:
BFS: