讲透树第2集 | 树的遍历复盘专题

image

1 前言

你们好,「树」的刷题已经有一段时间了。node

一来二去时间上有所拖延。以前总结过「树」的基础遍历,这一篇来具体题目看看,对于基础遍历能遇到哪些问题。python

下图是我们以前规定的「基础遍历」的一些题目。git

围绕,前中后序遍历对于N叉树的适用性,已经层序遍历不一样的打印方式,有哪些注意点。github

题目以下:markdown

102.二叉树的层序遍历 leetcode-cn.com/problems/bi…app

589.N 叉树的前序遍历 leetcode-cn.com/problems/n-…框架

107.二叉树的层序遍历 II leetcode-cn.com/problems/bi…oop

145.二叉树的后序遍历 leetcode-cn.com/problems/bi…post

94.二叉树的中序遍历 leetcode-cn.com/problems/bi…学习

429.N 叉树的层序遍历 leetcode-cn.com/problems/n-…

144.二叉树的前序遍历 leetcode-cn.com/problems/bi…

590.N 叉树的后序遍历 leetcode-cn.com/problems/n-…

以上,在 github 记录:github.com/xiaozhutec/…

2 分类

前面咱们说过一些基础的「树」的遍历,直达连接:mp.weixin.qq.com/s/nTB41DvE7…

有了以前的基础,很直观的,能够将上述遍历的题目分为两类两方面

两类: 第一类,分别是前序遍历、中序遍历和后续遍历 第二类,层次遍历 两方面: 第一方面,递归实现,适用于前序遍历、中序遍历和后续遍历 第二方面,非递归实现适用于前序遍历、中序遍历、后续遍历以及层次遍历

3 递归遍历

在以前 mp.weixin.qq.com/s/nTB41DvE7… 的一篇文章中已经详细说过了递归的方式,你们可从新翻开看看。

而后该篇文章因为涉及到 N 叉树的遍历方式,所以,我们对二叉树和N叉树进行对比代码设计,因为其类似性,很容易就破解这几个题目

首先,定义「树」结点类,二叉树和 N叉树的结点定义不一样点

# 二叉树
class TreeNode(object):
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

# N叉树
class Node(object):
    def __init__(self, val=None, children=[]):
        self.val = val
        self.children = children
复制代码

看起来区别是二叉树的左右孩子和 N叉树的孩子们结点的区别,其实本质是同样的,同理二叉树的左右孩子也能写成children列表的形式。

即:

# 二叉树
class TreeNode(object):
    def __init__(self, val, children=[]):
        self.val = val
        self.children = children
复制代码

而这里二叉树的 children 只有两个孩子,即左孩子 left 和右孩子 right。

描述完上述的孩子结点的定义,下面再看看递归的遍历方式。

# 二叉树
res = []
def pre_order(root):
    if not root:
        return
    res.append(root.val)
    pre_order(root.left)
    pre_order(root.right)
    
# N叉树
res = []
def pre_order(root):
    if not root:
        return
    res.append(root.val)
    for node in root.children:
        pre_order(node)
复制代码

看出区别了吗?区别就在 res.append(root.val) 以后的代码。

二叉树的写法是先递归左孩子,再递归右孩子。

N叉树的写法是,利用 for 循环,依次递归孩子结点。

因此,区别就是递归的写法,二叉树写两次递归,N叉树写N次递归

都是同样的逻辑,是否是很简单。相似于先序遍历,其中中序遍历和后续遍历,都是一样的逻辑去处理(N叉树,N可表明从2到n)

github 查看详细代码:github.com/xiaozhutec/…

4 非递归遍历

非递归遍历先序、中序和后续遍历的详细逻辑在也这里已经写了,能够返回头看看 mp.weixin.qq.com/s/nTB41DvE7…

层次遍历在「树-自顶向下」的类别中运用的比较多,相比较于递归解法,利用层次遍历更加容易理解,并且代码框架比较单一,具体的介绍和案例会在下一篇《讲透树 | 自顶向下类别题目专题》中进行介绍。

本文的非递归遍历主要针对的是层次遍历。那为何以前讲的比较明白了,这里又会说呢?

是这样,以前讲解的层次遍历,我们打印出来的形式和 LeetCode 题目中打印出来的形式是有些区别的。并且仅仅由于打印的区别,会引出不同的一个思路,后面不少状况都会遇到这样的思惟方式。

以下:

# 经典层次遍历打印形式
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
# LeetCode 中打印的形式
[['A'], ['B', 'C', 'D'], ['E', 'F', 'G', 'H', 'I']]
复制代码

是的,LeetCode 中打印的形式是要求将每一层的结点元素单独放到一个 list 中,最后再放置到一个大的 list 中。

传统层次遍历打印形式

先来简单回顾一下,传统的打印方式。

跟着上述图中的描述,动手画一下就会明白特别的简单。

一句话总结就是:

循环判断队列 queue 中是否有元素,若是有,访问该元素而且判断该结点元素是否有孩子结点,若是有,孩子结点依次入队 queue,不然,继续循环执行。

看代码:

while queue:
    node = queue.pop()
    res.append(node.val)
    if node.left:
        queue.appendleft(node.left)
    if node.right:
        queue.appendleft(node.right)
复制代码

就是不断判断队列中队首是够有元素,若是有元素,访问该结点元素,而且判断该结点是否有孩子结点,若是有,则依次入队。继续循环判断。

其实N叉树来讲的话,也是同样的逻辑,只不过是把二叉树中的两个孩子,换为N个孩子,进行入队操做。

while queue:
    node = queue.pop()
    res.append(node.val)
    for child_node in node.children:
      	queue.appendleft(child_node)
复制代码

同样的道理!是否是比较简单。

LeetCode 中题目打印方式

也是用一幅图来描述,相较于经典的层次遍历,每层单独存放会稍微复杂一些,须要变换一点点思路。

核心一句话

queue 中存放的是当前层的结点集,level_queue 存放的下一层的结点集合。

每次循环遍历当前层queue中结点的同时,将该层结点集写进一个临时list,将下一层的结点入队到 level_queue 中。以后将 level_queue 赋值给 queue,循环执行,直到 queue 为空。

最后,将每层临时 list 写到一个大的 list 中

「点击下图查看高清原图」👇

上图是以二叉树进行举例子,其实和 N叉树的逻辑几乎是一致的,我们先来看二叉树的相关核心代码:

def levelOrder(self, root):
    res = []
    if not root:
        return res
    queue = [root]
    while queue:
        level_queue = []      # 临时记录每一层结点
        level_res = []        # 临时记录每一行的结点值
        for node in queue:		# 循环遍历每一层全部的结点
            level_res.append(node.val)
            if node.left:
                level_queue.append(node.left)
            if node.right:
                level_queue.append(node.right)
        queue = level_queue
        res.append(level_res)
    return res
复制代码

相似的思路,来看看 N叉树的层次遍历代码:

def levelOrder_leetcode(self, root):
    res = []
    queue = [root]
    if not root:
        return res
    while queue:
        level_queue = []  	# 临时保存每一层结点元素便于下一次进行迭代
        level_res = []    	# 临时保存每一层结点值
        for node in queue:
            level_res.append(node.val)
            if node.children:
                for child_node in node.children:
                    level_queue.append(child_node)
        queue = level_queue
        res.append(level_res)
    return res
复制代码

到这里,应该已经很是熟悉二叉树和N叉树的一些区别了,依旧是在 level_res.append(node.val) 以后的几行代码的区别。

本篇文章主要是想说明开篇说的一些关于 LeetCode 上基础的「树遍历」的核心解决思路。

代码和本文的文档都在 github.com/xiaozhutec/… , 须要的小伙伴能够自行下载代码运行跑起来!

github 内容刚刚开始更新,感兴趣的能够一块儿来学习。私信我 “LeetCode刷题” 群里一块儿刷题进步。

手里有 star 的,有空能够帮我点点 star。谢过你们!