n种解法破DFS与BFS
- 2019 年 10 月 5 日
- 筆記

n种解法破DFS与BFS
0.说在前面1.二叉树的层次遍历I1.1 BFS非递归思路11.2 BFS非递归思路21.3 BFS双端队列1.4 BFS递归思路1.5 DFS递归思路2.二叉树的层次遍历II2.1 反转思路2.2 直接插入
0.说在前面
最近实在太忙了,但是算法我时刻不会忘记,每周都刷至少两道,比如我昨天上课无聊,拿手机刷了两道,感觉很easy,或许我刷的比较简答吧,然后今天回到实验室,看了之前给Datawhale编程实践定的任务,是一道层次遍历,于是这道题引发了我的思考,刚好晚上有时间,就花了时间来研究一波,结果真的很有趣。
一件事,简单而又直白;一件事,复杂而又晦涩;我宁愿选择后者,因为他可以激发你的潜能!
今天呢主要来介绍两道题,二叉树的层次遍历I与II,运用的思想为DFS与BFS,实现算法包含递归与非递归!
1.二叉树的层次遍历I
关于DFS与BFS这里不多做介绍,会在后面写出几篇简单文章让大家来看,如果有什么需求,可以留言!
【问题】
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
例如: 给定二叉树: [3,9,20,null,null,15,7]
,
3 / 9 20 / 15 7
返回其层次遍历结果:
[ [3], [9,20], [15,7] ]
1.1 BFS非递归思路1
【思路】
采用传统的队列思路,先入先出思想,层次遍历大家都知道吧,就是一行访问完毕,再访问下一行,很有顺序,而对于二叉树BFS与层次遍历一致,那么这里就是采用BFS队列的思路来操作!
【实现】
代码实现思路为:首先对特殊情况处理,然后定义一个queue(用来存放每层节点)及结果list。然后进入循环,再次建立一个空的list用来存放每层的节点值,然后对queue循环出队,对出队的节点操作(让左孩子与右孩子入队),所以在代码中引入了同层节点个数的变量length,主要是queue要做修改,所以会发生改变,自然不能直接遍历原先的queue!每层访问完毕,让这层的节点值list添加到结果list中,返回即可!
def levelOrder(self, root): res = [] if root is None: return res queue = [root] # 列表为空时,循环终止 # 可以省去len(queue)!=0 while queue and len(queue) != 0: # 使用列表存储同层节点 level_val = [] # 同层节点的个数 length = len(queue) for i in range(length): # 将同层节点依次出队 h = queue.pop(0) if h.left: # 左孩子入队 queue.append(h.left) if h.right: # 右孩子入队 queue.append(h.right) level_val.append(h.val) res.append(level_val) return res
1.2 BFS非递归思路2
【思路】
这个思路与上面类似,不同之处在于,这里在循环里面建立了两个list,而这两个list的作用分别为,一个用来存储每层节点值,一个用来存储每层的左右孩子节点,其实思路1与2基本一致,只是代码风格不一样,建议采用第一种思路写!
【实现】
def levelOrder(self, root): res = [] if not root: return [] res_list = [] root_list = [root] while root_list and len(root_list)!=0: # 每层节点值 val_list = [] # 每层左右孩子节点 level_list = [] for node in root_list: val_list.append(node.val) if node.left: level_list.append(node.left) if node.right: level_list.append(node.right) # 修改循环条件 root_list = level_list # 结果集中添加每层list res_list.append(val_list) return res_list
1.3 BFS双端队列
【思路】
思路同上1
【实现】
实现思路:采用了collections里面的双端队列deque!具体的用法注释标出了!
def levelOrder(self, root): res = [] if not root: return res # 导包 import collections # 双端队列定义 queue = collections.deque() queue.append(root) # visited = set(root) while queue: length = len(queue) level_val = [] for _ in range(length): # 弹出最左边的节点 node = queue.popleft() level_val.append(node.val) if node.left: queue.append(node.left) if node.right: queue.append(node.right) res.append(level_val) return res
1.4 BFS递归思路
【思路】
思路就是递归+BFS,具体思路看实现!
【实现】
首先在结果list中每层插入一个空list,然后循环每一层所有节点,将当前节点直接加入对应层当中,然后更新下一层节点list(更新方法为:将当前及节点的左右孩子入list即可),然后不断递归,直到下一层没有节点了,结束递归!
所以这里是一层层递归,一层层实现,所以名字为BFS递归!
def levelOrder(self, root): if root is None: return [] self.res = [] self.bfs([root], 0) return self.res def bfs(self, level_nodes, level): if not level_nodes: return self.res.insert(level, []) temp = [] for node in level_nodes: self.res[level].append(node.val) if node.left: temp.append(node.left) if node.right: temp.append(node.right) self.bfs(temp, level+1)
1.5 DFS递归思路
【思路】
思路就是采用深度优先搜索,先选择一个分支不断往下遍历,标记访问过的节点,在去继续往下,如果已经到达终点,回退各个分支节点,进入下一分支,重复前面步骤,直到所有节点访问完毕!
【实现】
在代码中体现深度优先搜索的为:
self.result[level].append(root.val)
这一行表示为当前层添加节点值!
具体的解释放在注释中!
def levelOrder(self, root): # 特殊情况处理 if not root: return [] # 定义结果list self.result = [] # 调用函数 self.dfs(root, 0) # 返回结果 return self.result # dfs函数 def dfs(self, root, level): # 这一行很关键,主要是用来为访问下一层的节点添加一个空的list if len(self.result) < level + 1: self.result.append([]) # 访问对应level的list,并添加数据 self.result[level].append(root.val) # 左孩子递归 if root.left: self.dfs(root.left, level + 1) # 右孩子递归 if root.right: self.dfs(root.right, level + 1)
对于上述的dfs递归的终止条件就是,没有左右孩子了,就结束了,为了更加好看,可以写成如下代码:
改写变动为终止条件变动及左右孩子访问判断条件变动!
def dfs(self, root, level): # 递归终止条件 if not root: return # 这一行很关键,主要是用来为访问下一层的节点添加一个空的list if len(self.result) < level + 1: self.result.append([]) # 访问对应level的list,并添加数据 self.result[level].append(root.val) # 左孩子递归 self.dfs(root.left, level + 1) # 右孩子递归 self.dfs(root.right, level + 1)
2.二叉树的层次遍历II
上面介绍了5种方法解决二叉树的层次遍历!那么下面又有一道题是对上面的简单处理,下面接着来看!
【问题】
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
例如: 给定二叉树 [3,9,20,null,null,15,7]
,
3 / 9 20 / 15 7
返回其自底向上的层次遍历为:
[ [15,7], [9,20], [3] ]
2.1 反转思路
反转思路为将一个list直接做反转即可,实现为:
list[::-1]
所以将上面的所有方法最后返回结果直接跟个[::-1],那么这道题就解决了!
2.2 直接插入
上面最后需要做反转,也就是切片,这样的话时间复杂度会变高,有没有更高效率了,当然有了,那就是直接插入了!
怎么改呢?
直接将上面的结果list的append改为insert(0,xxx),就是在每次数据的前面插入,这样就保证的先入的在栈底!也就满足了题意!