[算法][面试]二叉树非递归形式的遍历及层级遍历

tech2022-12-02  92

层级遍历

这里提供了三种方法:

指针标识法对象标识法队列计数法

从代码量和理解上来说,第三种方法是占优的。

def level_order(root) -> List[List[int]]: # 左程云所使用的方法,略繁琐,一般不用 if not root: return [] queue, res = [root], [] line_res, last_one, nxt_last_one = [], root, None while queue: root = queue.pop() line_res.append(root.v) if root.left: queue.insert(0, root.left) nxt_last_one = root.left if root.right: queue.insert(0, root.right) nxt_last_one = root.right if root == last_one: res.append(line_res) line_res = [] last_one = nxt_last_one return res def level_order_2(root) -> List[List[int]]: level_sign = "OVER" # 每层的结束标记符,也可以new一个Object()来替代 queue, res = [level_sign, root], [[]] # 注意初始化条件 while len(queue) > 1: root = queue.pop() if root == level_sign: res.append([]) # 添加一个新子列表用于保存新层的节点 queue.insert(0, level_sign) # 插入该层的结束标记符号 elif root is not None: res[-1].append(root.v) # 最新层的子列表添加、保存节点值 queue.insert(0, root.left) queue.insert(0, root.right) return res[:-1] # 丢弃最后一个插入的空列表(最后一层都是空节点) def level_order_3(root) -> List[List[int]]: queue, res = [root], [] while queue: res.append([]) n = len(queue) for _ in range(n): # 将当前队列在上一层中所有节点遍历完 root = queue.pop() if root is not None: res[-1].append(root.v) queue.insert(0, root.left) queue.insert(0, root.right) return res[:-1]

锯齿遍历也可以基于层级遍历修改,即为奇数层res[-1].append(root.v),偶数层res[-1].insert(0, root.v)。

非递归遍历

1. 模板方法

先上一个模板法,同时可以非递归的得到二叉树的前序和中序遍历:

def pre_template(root): st, res = [], [] while st or root: if root: # 入栈,走向左子树 res.append(root.v) # 先序遍历输出 st.append(root) root = root.left else: # 出栈,走向它的右子树 root = st.pop() res.append(root.v) # 中序遍历输出 root = root.right return res

模板法的理解和栈回溯有关,建议自己画一棵树理解,能记很久。

模板法后序遍历二叉树需要进行一些小修改:

def pos_template(root): st, res = [], [] state = {} # 访问状态表 while st or root: if root: # 入栈,走向左子树 state[root] = "in" # 刚入栈 st.append(root) root = root.left else: # 出栈,走向它的右子树 if st[-1] in state: # 栈顶元素第一次被访问 state.pop(st[-1]) # 第一次回到这个点,出状态表,即标记为可出栈状态 root = st[-1].right # 走向右子树,但保留栈状态 else: res.append(st.pop().v) # 出栈、后续输出 root = None # 置为空 return res

——主要的改动在于else: # 出栈,走向它的右子树之后的代码,利用了一个状态访问表对访问次数进行计数,当且仅当某个节点被二次访问后才退栈并被输出。请参考这篇文章中的图例进行理解。

2. 利用函数栈与根节点的性质

特别的,前序遍历还可以使用另外一种更简短的方法:

def pre_short(root): st, res = [root], [] while st: node = st.pop() if node is not None: res.append(node.v) # 先序输出 st += [node.right, node.left] # 先压右后压左 return res

由于该方法利用了根节点和函数栈性质,因此无法适用于中序遍历,但可以修改成后序遍历的版本:

def pos_short(root): st, res = [root], [] while st: node = st.pop() if node is not None: res.insert(0, node.v) # 读值的时候倒着入栈 st += [node.left, node.right] # 正着入栈即可 return res
最新回复(0)