层级遍历
这里提供了三种方法:
- 指针标识法
- 对象标识法
- 队列计数法
从代码量和理解上来说,第三种方法是占优的。
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