树相关知识:
树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。
它具有以下的特点:
1. 每个节点有零个或多个子节点;
2. 没有父节点的节点称为根节点;
3.每一个非根节点有且只有一个父节点;
4.除了根节点外,每个子节点可以分为多个不相交的子树。
一些关于树的基本专业术语:
结点的度:结点拥有的子树的数目。
叶子:度为零的结点。
分支结点:度不为零的结点。
树的度:树中结点的最大的度。
层次:根结点的层次为1,其余结点的层次等于该结点的双亲结点的层次加1。
树的高度:树中结点的最大层次。
无序树:如果树中结点的各子树之间的次序是不重要的,可以交换位置。
有序树:如果树中结点的各子树之间的次序是重要的, 不可以交换位置。
森林:0个或多个不相交的树组成。对森林加上一个根,森林即成为树;删去根,树即成为森林。
二叉树相关知识:
二叉树是每个节点最多有两个子树的树结构。具有如下性质:
性质1:二叉树第i层上的结点数目最多为 2{i-1} (i≥1)。
性质2:深度为k的二叉树至多有2{k}-1个结点(k≥1)。
性质3:包含n个结点的二叉树的高度至少为log2 (n+1)。
性质4:在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1。
满二叉树:高度为h,并且由2{h} –1个结点的二叉树,被称为满二叉树。
完全二叉树:一棵二叉树中,只有最下面两层结点的度可以小于2,并且最下一层的叶结点集中在靠左的若干位置上。一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。
二叉搜索树:要求每个节点本身大于其左子树,而小于其右子树,对其进行中序遍历后,会得到一个有序的列表,这是我们经常用到的一种数的结构。
平衡二叉树:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树,并且满足二叉搜索树的规则。
二叉树的遍历:分为前序遍历,中序遍历和后序遍历。
前序遍历:根结点 ---> 左子树 ---> 右子树
中序遍历:左子树---> 根结点 ---> 右子树
后序遍历:左子树 ---> 右子树 ---> 根结点
力扣94二叉树的中序遍历:
对于这道题,我们可以分别采用递归和迭代两种解法来进行遍历。首先,递归是比较简单直观的。(这个视频对递归讲的很好,可以看看 二叉树递归路径图解_哔哩哔哩_bilibili)
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]):
result = [] # 初始化一个数组记录结果
self.helper(root, result) # 定义辅助函数完成遍历
return result
def helper(self, node, result):
if node is None: # 节点是空直接return
return
self.helper(node.left, result) # 递归遍历其左子树
result.append(node.val) # 先左再中间再右
self.helper(node.right, result)
# 递归法(中序遍历)
# N是节点数,H是树的高度
# time complexity: O(N)
# space cOmplexity: O(H)
然后是迭代法:由于栈的先进后出原则,我们可以把树从根节点开始往左依次放进栈中,指针指向空即最左边已经没有左子树的节点,我们开始取出栈顶元素,由栈的性质保证左-根-右的特性输出。
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]):
result = [] # 初始化一个数组记录结果
stack = [] # 指定一个辅助的栈
cur = root # 指针从根节点开始
while(cur or stack): # 指针指向空节点或者栈为空停止循环
if cur: # 只要不指向空节点,就执行下面的语句
stack.append(cur) # 把这个节点加入栈中
cur = cur.left # 指针指向该节点的左子树
else: # 当指针指向空时,
cur = stack.pop() # 把栈顶元素取出
result.append(cur.val) # cur指向的值加入result中
cur = cur.right # 指针指向节点(栈顶元素)的右子树
return result
# 迭代法(中序遍历)
# N是节点数,H是树的高度
# time complexity: O(N)
# space cOmplexity: O(H)
下面来看一下大佬写的颜色标记法(可以作为前中后序遍历的模板,只需调整三句代码的顺序即可分别完成前中后序遍历)
使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色。如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点依次入栈。如果遇到的节点为灰色,则将节点的值输出。(详情可以看看力扣链接力扣)
class Solution:
def inorderTraversal(self, root: TreeNode):
WHITE, GRAY = 0, 1 # 白遍历。灰输出
result = []
stack = [(WHITE, root)] # 初始化栈放入白和根节点
while stack: # 栈不为空进入循环
color, node = stack.pop() # 取出栈顶元素
if node is None: continue # 如果节点为空继续执行
if color == WHITE: # 白,遍历,添加元素入栈
stack.append((WHITE, node.right)) # 中序遍历左根右,入栈顺序相反
stack.append((GRAY, node))
stack.append((WHITE, node.left))
else:
result.append(node.val) # 灰色加入结果集
return result
# 颜色标记法(中序遍历)
# time complexity: O(N) 遍历了所有节点
# space complexity: O(N) 产生了新的数组result
力扣144二叉树的前序遍历
对于这道题,我们和中序遍历一样采用递归法,迭代法和颜色标记法来做。只需交换部分代码顺序即可。
递归:
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]):
result = []
self.helper(root, result)
return result
def helper(self, node, result):
if node is None:
return
result.append(node.val)
self.helper(node.left, result)
self.helper(node.right, result)
# 递归法(前序遍历)
# N是节点数,H是树的高度
# time complexity: O(N)
# space cOmplexity: O(H)
迭代:由于前序遍历是根-左-右的顺序,所以在把根节点保存后先右后左的顺序保存进栈中,取出时就会形成先左后右的结果。
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]):
# 如果根节点为空,直接返回空列表
if not root:
return []
stack = [root] # 初始化栈加入根节点
result = [] # 结果数组保存结果
while stack: # 当栈为空停止循环
node = stack.pop() # 节点入栈
result.append(node.val) # 将根节点值加入结果
if node.right: # 如果右子树非空
stack.append(node.right) # 右子树入栈
if node.left: # 如果左子树非空
stack.append(node.left) # 左子树入栈
return result
# 迭代法(前序遍历)
# N是节点数,H是树的高度
# time complexity: O(N) 遍历了所有节点
# space complexity: O(H) 产生了新的数组result
颜色标记法:
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]):
WHITE, GRAY = 0, 1 # 白遍历。灰输出
result = []
stack = [(WHITE, root)] # 初始化栈放入白和根节点
while stack: # 栈不为空进入循环
color, node = stack.pop() # 取出栈顶元素
if node is None: continue # 如果节点为空继续执行
if color == WHITE: # 白,遍历,添加元素入栈
stack.append((WHITE, node.right)) # 前序遍历根左右,入栈顺序相反
stack.append((WHITE, node.left))
stack.append((GRAY, node))
else:
result.append(node.val) # 灰色加入结果集
return result
# 颜色标记法(前序遍历)
# time complexity: O(N) 遍历了所有节点
# space complexity: O(N) 产生了新的数组result
力扣145二叉树的后序遍历:
递归:同样只是交换顺序即可
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]):
result = []
self.helper(root, result)
return result
def helper(self, node, result):
if node is None:
return
self.helper(node.left, result)
self.helper(node.right, result)
result.append(node.val)
# 递归法(后序遍历)
# N是节点数,H是树的高度
# time complexity: O(N)
# space cOmplexity: O(H)
迭代:(画图比较好理解)
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]):
# 如果根节点为空,直接返回空列表
if not root:
return []
stack = []
result = []
while root or stack:
while root: # 当root不为None执行循环
stack.append(root) # 当前节点入栈
if root.left: # 如果当前节点有左子树,继续向左子树找
root = root.left
else: # 如果当前节点无左子树,在右子树继续找
root = root.right
# 跳出循环的条件是 root 为空,那当前栈顶元素为叶子节点。
cur = stack.pop() # 弹出栈顶元素,并加入结果数组
result.append(cur.val)
# 如果栈不为空,且当前栈顶元素的左节点是刚刚跳出的栈顶元素 cur
# 则转向遍历右子树当前栈顶元素的右子树,stack[-1]代表栈顶元素
if stack and stack[-1].left == cur:
root = stack[-1].right
# 否则证明当前栈顶元素无左右子树,那当前的栈顶元素弹出。
else:
root = None
return result
# 迭代法(后序遍历)
# N是节点数,H是树的高度
# time complexity: O(N) 遍历了所有节点
# space complexity: O(H) 产生了新的数组result
颜色标记法:(yyds,同样只需调换入栈顺序即可)
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]):
# 如果根节点为空,直接返回空列表
if not root:
return []
WHITE, GRAY = 0, 1 # 白遍历。灰输出
result = []
stack = [(WHITE, root)] # 初始化栈放入白和根节点
while stack: # 栈不为空进入循环
color, node = stack.pop() # 取出栈顶元素
if node is None: continue # 如果节点为空继续执行
if color == WHITE: # 白,遍历,添加元素入栈
stack.append((GRAY, node)) # 后序遍历左右根,入栈顺序相反
stack.append((WHITE, node.right))
stack.append((WHITE, node.left))
else:
result.append(node.val) # 灰色加入结果集
return result
# 颜色标记法(前序遍历)
# time complexity: O(N) 遍历了所有节点
# space complexity: O(N) 产生了新的数组result
这个二叉树的遍历前前后后看了一周了,虽然中途有很多其他事忙,最后还是写出来了,中途参考了很多大佬的答案与思路,总结出三种解法,好好学习。