Leetcode785. Is Graph Bipartite?
Leetcode785. Is Graph Bipartite?
题目
思路
- 能将节点分成两个集合 U / V U/V U/V的图,就是二部图,若一个节点既能在 U U U,也能在 V V V中,那就不是二部图
- DFS(Depth First Search,深度优先搜索)遍历着色,用两种颜色的画笔,交替地对遇到的节点着上不同颜色,未着色节点进行着色,并换成另一支画笔,若下一个节点已经着色,且颜色与手中的画笔不同(这个节点可以同时在 U U U和 V V V中),则判断不是二部图
- DFS可以用递归,也可以用迭代进行
复杂度
-
时间复杂度 O ( n ) \mathcal{O}(n) O(n)
所有的节点只访问一次,因此时间复杂度为 O ( n ) \mathcal{O}(n) O(n) -
空间复杂度 O ( n ) \mathcal{O}(n) O(n)
空间复杂度上,递归的最坏情况,正好是迭代的最好情况,反过来不成立
- 递归,在一条路径上需要递归到最后一个节点才开始返回
最坏情况,所有节点都在一条路径上, O ( n ) \mathcal{O}(n) O(n)
最好情况,所有节点出入度都为1,即每个节点都只与一个节点相连,那么递归深度只有1,只保存了来路上的一个节点, O ( 1 ) \mathcal{O}(1) O(1) - 迭代,访问一个节点时,将这个节点相连的所有节点加入stack中
最坏情况, U U U中只有一个节点 u u u, V V V中的每个节点都与这个节点相连,访问 u u u时,将与它相连的所有节点,即 V V V中的所有节点都加入stack中, O ( n ) \mathcal{O}(n) O(n)
最好情况,所有节点都在一条路径上,每次加入的子节点只有一个, O ( 1 ) \mathcal{O}(1) O(1)
代码
递归DFS遍历
class Solution:
def isBipartite(self, graph: List[List[int]]) -> bool:
colored = {}
def dfs(i, color):
if i in colored: # 对已着色的判断是否矛盾
return colored[i] == color
# 对未着色的进行着色
colored[i] = color
# DFS访问相邻节点
for nxt in graph[i]:
if not dfs(nxt, color * (-1)):
return False
# 顺利完成一条路径的dfs,没有产生冲突
return True
# 以图中的每个节点作为起点,进行dfs搜索
for ii, g in enumerate(graph):
# 当前点有相邻节点且未着色,则进入dfs
if g and ii not in colored:
# 任意一条路径返回False,则不是二部图
if not dfs(ii, 1):
return False
return True
借助于栈的DFS遍历-迭代
class Solution:
def isBipartite(self, graph: List[List[int]]) -> bool:
if graph == []:
return True
stack = []
color = {}
visit = [False] * len(graph)
# 借助于栈的DFS遍历
while stack or (False in visit):
if stack == []:
# 将第一个未访问节点放入stack,并着色
first_unvisited = visit.index(False)
stack.append(first_unvisited)
color[first_unvisited] = 1
# 弹出,标记为已访问
ind = stack.pop()
if visit[ind] == False:
visit[ind] = True
# DFS访问相邻节点
for n in graph[ind]:
if n in color:
# 对已着色的判断是否矛盾
if color[n] == color[ind]:
return False
else: # 对未着色的进行着色
color[n] = -1 * color[ind]
# 将子节点加入stack,待下次循环访问
stack.append(n)
# 循环正常结束,说明是二部图(没有出现着色矛盾)
return True
漫谈
update:新开一篇在递归和迭代对比-栈结构
在前一篇Leetcode230. Kth Smallest Element in a BST中,对树进行遍历时,也有两种方式:递归和迭代
好像两个总是如影相随,细想一想:
递归的实现,也是借助于栈,只不过在程序中不可见,栈中保存的是当前节点的所有父节点,是访问到当前节点的路径,记忆的是来路,同级的兄弟节点是没有保存到栈中的
而迭代中的栈在程序中可见,直接拿出来用,用来保存当前节点的所有子节点,然后在访问的时候,由于当前节点已经弹出,所以,记忆的是去路
总结一下
栈的级别 | 栈在程序中是否可见 | 栈中保存的记忆 | 是否保存同级节点 | |
---|---|---|---|---|
递归 | 编译器 | 否 | 父节点,来路 | 否 |
迭代 | 用户程序 | 是 | 子节点,去路 | 是 |