二叉树遍历算法-递归、迭代(深度优先搜索、广度优先搜索)

文章目录

  • 前言
  • 一、二叉树的递归遍历
    • 前中后序遍历
    • N 叉树的前序遍历(leetcode 589.)
    • N 叉树的后序遍历(leetcode 590.)
  • 二、用栈来实现递归
    • 二叉树的前序遍历(leetcode 144.)
    • 二叉树的中序遍历(leetcode 94.)
    • 二叉树的后序遍历(leetcode 145.)
  • 三、二叉树层序遍历
    • 二叉树的层序遍历(leetcode 102.)
    • 二叉树的层次遍历 II(leetcode 107.)
    • 二叉树的右视图(leetcode 199.)


前言

二叉树的前中后序遍历方法,有递归和迭代(非递归),还有一种厉害的遍历方式:morris遍历(该遍历比较小众,面试几乎不会考)。但是morris遍历是二叉树遍历算法的超强进阶算法,morris遍历可以将非递归遍历中的空间复杂度降为O(1),具体没有仔细研究过,这里不做介绍。


一、二叉树的递归遍历

递归三要素:
1. 确定递归函数的参数和返回值。确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数,并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
2. 确定终止条件。操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
3. 确定单层递归的逻辑。 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。

前中后序遍历

1.确定递归函数的参数和返回值:因为要打印出前序遍历节点的数值,所以参数里需要传入vector来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数无返回类型。
2.确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return。
3.确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值。
单层递归的逻辑就是按照中左右的顺序来处理的,这样二叉树的前序遍历,基本就写完了,再看一下完整代码:
前序遍历

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
	def preorderTraversal(self,root:TreeNode):
		if not root:
			return []
		left=self.preorderTraversal(root.left)
		right=self.preorderTraversal(root.right)
		return [root.val]+left+right	

中序遍历

class Solution:
	def inorderTraversal(self,root:TreeNode):
		if not root:
			return []
		left=self.inorderTraversal(root.left)
		right=self.inorderTraversal(root.right)
		return left+[root.val]+right

后续遍历

class Solution:
	def postorderTraversal(self,root:TreeNode):
		if not root:
			return []
		left=self.postorderTraversal(root.left)
		right=self.postorderTraversal(root.right)
		return left+right+[root.val]

N 叉树的前序遍历(leetcode 589.)

思路:N叉树的前序遍历是指先访问根节点,然后按照从左到右的顺序依次遍历子节点。

"""
# Definition for a Node.
class Node:
    def __init__(self, val=None, children=None):
        self.val = val
        self.children = children
"""
#递归法
class Solution:
    def preorder(self, root: 'Node') -> List[int]:
        ans=[]
        def dfs(node):
            if not node:
                return []
            ans.append(node.val)
            for child in node.children:
                dfs(ch)
        dfs(root)
        return ans

复杂度分析:
时间复杂度:O(m),其中m为N叉树的节点。每个节点恰好被遍历一次。
空间复杂度:O(m),递归过程中需要调用栈的开销,平均情况下为 O(log⁡m),最坏情况下树的深度为m−1,此时需要的空间复杂度为O(m)。

N 叉树的后序遍历(leetcode 590.)

N叉树的后序遍历是指先遍历子节点,然后访问根节点。

"""
# Definition for a Node.
class Node:
    def __init__(self, val=None, children=None):
        self.val = val
        self.children = children
"""
#递归法
class Solution:
    def postorder(self, root: 'Node') -> List[int]:
        ans=[]
        def dfs(node):
            if node is None:
                return []
            for child in node.children:
                dfs(ch)
            ans.append(node.val)
        dfs(root)
        return ans

复杂度分析:
时间复杂度:O(m),其中m为N叉树的节点。每个节点恰好被遍历一次。
空间复杂度:O(m),递归过程中需要调用栈的开销,平均情况下为 O(log⁡m),最坏情况下树的深度为m−1,需要的空间为O(m−1),因此空间复杂度为O(m)。

二、用栈来实现递归

用迭代法通过栈来实现递归

二叉树的前序遍历(leetcode 144.)

迭代法:

class Solution:
	def preOrder(self,root):
		#根结点为空,则返回空列表
		if not root:
			return []
		stack=[root]#吧根结点存入栈
		result=[]
		while stack:
			node=stack.pop()#栈中弹出根结点也就是中结点
			#中结点先处理
			result.append(node)#将中结点存入返回列表中
			#右孩子先入栈,这样栈弹出的时候才能先弹出左孩子
			if node.right:
				stack.append(node.right)
			if node.left:
				stack.append(node.left)
		return result

二叉树的中序遍历(leetcode 94.)

迭代法:

class Solution:
	def inOrder(self,root):
		if not root:
			return []
		stack=[]#不能提前将root结点放入stack中
		result=[]
		cur=root
		while cur or stack:
			#先迭代访问最底层的左子树结点
			if cur:
				stack.append(cur)
				cur=cur.left
			# 到达最左结点后处理栈顶结点  
			else:
				cur=stack.pop()
				result.append(cur.val)
				#取栈顶元素的右结点
				cur=cur.right
		return result

二叉树的后序遍历(leetcode 145.)

class Solution:
	def postOrder(self,root):
		if not root:
			return []
		stack=[root]
		result=[]
		while stack:
			node=stack.pop()
			#中结点先处理
			result.append(node)
			# 左孩子先入栈
           if node.left:
               stack.append(node.left)
           # 右孩子后入栈
           if node.right:
               stack.append(node.right)
       # 将最终的数组翻转
       return result[::-1]

二叉树遍历算法-递归、迭代(深度优先搜索、广度优先搜索)_第1张图片
我们这块用迭代法写出了二叉树的前后中序遍历,可以看出前序和中序是完全两种代码风格,并不像递归写法那样代码稍做调整,就可以实现前后中序。
这是因为前序遍历中访问节点(遍历节点)和处理节点(将元素放进result数组中)可以同步处理,但是中序就无法做到同步!(建议自己亲手用迭代法,先写出来前序,再试试能不能写出中序,就能理解了。)
那么问题又来了,难道二叉树前后中序遍历的迭代法实现,就不能风格统一么(即前序遍历改变代码顺序就可以实现中序 和后序)。
递归与迭代方法总结:
在实现迭代法的过程中,有同学问了:递归与迭代究竟谁优谁劣呢?
从时间复杂度上其实迭代法和递归法差不多(在不考虑函数调用开销和函数调用产生的堆栈开销),但是空间复杂度上,递归开销会大一些,因为递归需要系统堆栈存参数返回值等等。
递归更容易让程序员理解,但收敛不好,容易栈溢出。
这么说吧,递归是方便了程序员,难为了机器(各种保存参数,各种进栈出栈)。
在实际项目开发的过程中我们是要尽量避免递归!因为项目代码参数、调用关系都比较复杂,不容易控制递归深度,甚至会栈溢出。
面试:一定要掌握前中后序一种迭代的写法,并不因为某种场景的题目一定要用迭代,而是现场面试的时候,面试官看你顺畅的写出了递归,一般会进一步考察能不能写出相应的迭代。


三、二叉树层序遍历

这部分我们介绍二叉树的另一种遍历方式(图论中广度优先搜索在二叉树上的应用)即:层序遍历。层序遍历遍历相对容易一些,只要掌握基本写法(也就是框架模板),剩下的就是在二叉树每一行遍历的时候做逻辑修改。
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。而这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。

二叉树的层序遍历(leetcode 102.)

class TreeNode:
	def __init__(self,val=0,left=None,right=None):
		self.val=val
		self.left=left
		self.right=right
class Solution:
	def levelOrder(self,root):
		if not root:
			return []
		queue=collections.deque([root])
		result=[]
		while queue:
			level=[]
			for _ in range(len(queue)):
				cur=queue.popleft()
				level.append(cur.val)
				if cur.left:
					queue.append(cur.left)
				if cur.right:
					queue.append(cur.right)
			result.append(level)
		return result		
# 递归法
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        levels = []
        self.helper(root, 0, levels)
        return levels
    
    def helper(self, node, level, levels):
        if not node:
            return
        if len(levels) == level:
            levels.append([])
        levels[level].append(node.val)
        self.helper(node.left, level + 1, levels)
        self.helper(node.right, level + 1, levels)

二叉树的层次遍历 II(leetcode 107.)

修改上面两三行代码就可以完成下面的二叉树层序遍历
思路:
相对于102.二叉树的层序遍历,就是最后把result数组反转一下就可以了。

class Solution:
    """二叉树层序遍历II迭代解法"""

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def levelOrderBottom(self, root: TreeNode) -> List[List[int]]:
        if not root:
            return []
        queue = collections.deque([root])
        result = []
        while queue:
            level = []
            for _ in range(len(queue)):
                cur = queue.popleft()
                level.append(cur.val)
                if cur.left:
                    queue.append(cur.left)
                if cur.right:
                    queue.append(cur.right)
            result.append(level)
        return result[::-1]

二叉树的右视图(leetcode 199.)

给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
二叉树遍历算法-递归、迭代(深度优先搜索、广度优先搜索)_第2张图片
思路:
层序遍历的时候,判断是否遍历到单层的最后面的元素,如果是,就放进result数组中,随后返回result就可以了。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def rightSideView(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        
        queue = collections.deque([root])
        right_view = []
        
        while queue:
            level_size = len(queue)
            
            for i in range(level_size):
                node = queue.popleft()
                
                if i == level_size - 1:
                    right_view.append(node.val)
                
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
        
        return right_view

总结,本篇文章都是讲解了二叉树的遍历方式,从递归到迭代,从深度遍历到广度遍历。二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现(此处我们又发现队列的一个应用了)。学完了思路我们真的可以一口气打十个(相信自己!!!):
102.二叉树的层序遍历
107.二叉树的层次遍历II
199.二叉树的右视图
637.二叉树的层平均值
429.N叉树的层序遍历
515.在每个树行中找最大值
116.填充每个节点的下一个右侧节点指针
117.填充每个节点的下一个右侧节点指针II
104.二叉树的最大深度
111.二叉树的最小深度
(ps感兴趣的同学可以添加本人微信公众号:HEllO算法笔记。还有机器学习基础知识点分享!)

你可能感兴趣的:(刷题,数据结构,算法,leetcode,二叉树遍历)