学习记录@代码随想录day14:二叉树part01
理论基础
二叉树分类
满二叉树
满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
完全二叉树
完全二叉树:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。
二叉树搜索
二叉搜索树是有数值的,二叉搜索树是一个有序树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
平衡二叉搜索树
平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
二叉树的存储方式
二叉树可以链式存储,也可以顺序存储。
那么链式存储方式就用指针, 顺序存储的方式就是用数组。
顾名思义就是顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在各个地址的节点串联一起。
用数组来存储二叉树如何遍历的呢?
如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
二叉树的遍历方式
二叉树主要有两种遍历方式:
深度优先遍历:先往深走,遇到叶子节点再往回走。
广度优先遍历:一层一层的去遍历。
这两种遍历是图论中最基本的两种遍历方式。
- 深度优先遍历
– 前序遍历(递归法,迭代法)
– 中序遍历(递归法,迭代法)
– 后序遍历(递归法,迭代法) - 广度优先遍历
– 层次遍历(迭代法)
这里前中后,其实指的就是中间节点的遍历顺序。
二叉树的定义
在Python中,可以使用类来定义二叉树。一个二叉树由节点组成,每个节点包含一个值和指向左子树和右子树的指针。
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
在这个示例中,TreeNode 类有一个构造函数 init,它接受一个参数 value,用于初始化节点的值。left 和 right 分别是指向左子树和右子树的指针,默认为 None。
通过创建 TreeNode 类的实例,我们可以构建二叉树。例如,下面的代码创建了一个简单的二叉树:
# 创建节点
root = TreeNode(1)
node2 = TreeNode(2)
node3 = TreeNode(3)
# 构建二叉树结构
root.left = node2
root.right = node3
递归遍历
递归算法三要素:
1.确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
2.确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
3.确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
前序遍历
'''
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
'''
题目链接:https://leetcode.cn/problems/binary-tree-preorder-traversal/
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
# 如果根节点为空,直接返回空列表
if not root:
return []
# 递归调用前序遍历函数,获取左子树的遍历结果
left = self.preorderTraversal(root.left)
# 递归调用前序遍历函数,获取右子树的遍历结果
right = self.preorderTraversal(root.right)
# 将根节点的值与左子树和右子树的遍历结果合并成一个列表,并返回
return [root.val] + left + right
后续遍历
题目链接:https://leetcode.cn/problems/binary-tree-postorder-traversal/
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
# 如果根节点为空,直接返回空列表
if root is None:
return []
# 递归调用后序遍历函数,获取左子树的遍历结果
left = self.postorderTraversal(root.left)
# 递归调用后序遍历函数,获取右子树的遍历结果
right = self.postorderTraversal(root.right)
# 将左子树和右子树的遍历结果与根节点的值合并成一个列表,并返回
return left + right + [root.val]
中序遍历
题目链接:https://leetcode.cn/problems/binary-tree-inorder-traversal/
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
# 如果根节点为空,直接返回空列表
if root is None:
return []
# 递归调用中序遍历函数,获取左子树的遍历结果
left = self.inorderTraversal(root.left)
# 递归调用中序遍历函数,获取右子树的遍历结果
right = self.inorderTraversal(root.right)
# 将左子树的遍历结果、根节点的值和右子树的遍历结果合并成一个列表,并返回
return left + [root.val] + right
迭代遍历
迭代法:使用循环语句和一些变量来反复执行某个操作,直到达到目标或满足终止条件为止。
'''前序遍历'''
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
# 如果根节点为空,直接返回空列表
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
'''
对于按层次为 1234567 的二叉树,其前序遍历的顺序是 1 2 4 5 3 6 7。
入栈的顺序如下:
将根节点 1 入栈;
弹出栈顶节点 1,将其值添加到遍历结果中;
将右子节点 3 入栈;
将左子节点 2 入栈;
弹出栈顶节点 2,将其值添加到遍历结果中;
将右子节点 5 入栈;
将左子节点 4 入栈;
弹出栈顶节点 4,将其值添加到遍历结果中;
将右子节点 null 入栈;
将左子节点 null 入栈;
弹出栈顶节点 null,将其丢弃;
弹出栈顶节点 null,将其丢弃;
弹出栈顶节点 5,将其值添加到遍历结果中;
将右子节点 null 入栈;
将左子节点 null 入栈;
弹出栈顶节点 null,将其丢弃;
弹出栈顶节点 null,将其丢弃;
弹出栈顶节点 3,将其值添加到遍历结果中;
将右子节点 7 入栈;
将左子节点 6 入栈;
弹出栈顶节点 6,将其值添加到遍历结果中;
将右子节点 null 入栈;
将左子节点 null 入栈;
弹出栈顶节点 null,将其丢弃;
弹出栈顶节点 null,将其丢弃;
弹出栈顶节点 7,将其值添加到遍历结果中;
将右子节点 null 入栈;
将左子节点 null 入栈;
弹出栈顶节点 null,将其丢弃;
弹出栈顶节点 null,将其丢弃。
最终,遍历结果为 1 2 4 5 3 6 7。
'''
从例子来看就比较容易理解了,每次将栈顶元素出栈,出栈节点的右节点先入栈,左节点后入栈。
#中序遍历
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
# 如果根节点为空,直接返回空列表
if not root:
return []
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
'''
如果一棵二叉树按层次是1234567
将根节点 1 入栈,此时 cur 指向 1;
将左子节点 2 入栈,此时 cur 指向 2;
将左子节点 4 入栈,此时 cur 指向 4;
已经到达最左结点 4,弹出栈顶结点 4 并将其值添加到遍历结果中,此时 cur 为 null;
弹出栈顶结点 2 并将其值添加到遍历结果中,此时 cur 指向 5;
将右子结点 5 入栈,此时 cur 指向 5;
已经到达最左结点 5,弹出栈顶结点 5 并将其值添加到遍历结果中,此时 cur 为 null;
弹出栈顶结点 1 并将其值添加到遍历结果中,此时 cur 指向 3;
将右子节点 3 入栈,此时 cur 指向 6;
将左子节点 6 入栈,此时 cur 指向 4;
已经到达最左结点 6,弹出栈顶结点 6 并将其值添加到遍历结果中,此时 cur 为 null;
将右子节点 7 入栈,此时 cur 指向 7;
已经到达最左结点 7,弹出栈顶结点 7 并将其值添加到遍历结果中,此时 cur 为 null;
弹出栈顶结点 3 并将其值添加到遍历结果中,此时 cur 为 null;
完成遍历,返回结果列表 result。
迭代次数 cur 入栈元素 出栈元素
1 1 1
2 2 2
3 4 4
4 null 4
5 5 5
6 null 5
7 null 2
8 3 3
9 6 6
10 4 7
11 null 6
12 7 7
13 null 7
14 null 3
'''
具体算法流程如下:
如果根节点为空,直接返回空列表;
初始化栈 stack 和结果列表 result,同时设置当前节点指针 cur 指向根节点;
当当前节点指针 cur 不为 None 或栈 stack 不为空时,执行以下操作:
如果当前节点 cur 不为 None,将其入栈,并将当前节点指针 cur 指向其左子节点;
如果当前节点 cur 为 None,弹出栈顶节点,将其值添加到结果列表 result 中,并将当前节点指针 cur 指向其右子节点;
返回结果列表 result。
后序遍历
# 后序遍历
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
stack = [root]
result = []
while stack:
node = stack.pop()
# 中结点先处理
result.append(node.val)
# 左孩子先入栈
if node.left:
stack.append(node.left)
# 右孩子后入栈
if node.right:
stack.append(node.right)
# 将最终的数组翻转
return result[::-1]
统一迭代
针对前、中、后迭代遍历中的方法,风格不统一。
要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法。
#前序
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
result = [] # 创建一个列表,用于存储遍历结果
st = [] # 创建一个栈,用于存储待处理的节点
if root:
st.append(root) # 如果根节点不为空,将根节点入栈
while st:
node = st.pop() # 弹出栈顶节点
if node != None:
if node.right: # 如果右子节点存在,将其入栈(空节点不入栈)
st.append(node.right)
if node.left: # 如果左子节点存在,将其入栈(空节点不入栈)
st.append(node.left)
st.append(node) # 将当前节点入栈
st.append(None) # 加入空节点作为标记,表示当前节点已经访问过但还未处理
else:
node = st.pop() # 弹出空节点
result.append(node.val) # 将弹出的节点值添加到遍历结果中
return result # 返回遍历结果
#中序
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
result = []
st = []
if root:
st.append(root)
while st:
node = st.pop()
if node != None:
if node.right: #添加右节点(空节点不入栈)
st.append(node.right)
st.append(node) #添加中节点
st.append(None) #中节点访问过,但是还没有处理,加入空节点做为标记。
if node.left: #添加左节点(空节点不入栈)
st.append(node.left)
else: #只有遇到空节点的时候,才将下一个节点放进结果集
node = st.pop() #重新取出栈中元素
result.append(node.val) #加入到结果集
return result
#后续
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
result = []
st = []
if root:
st.append(root)
while st:
node = st.pop()
if node != None:
st.append(node) #中
st.append(None)
if node.right: #右
st.append(node.right)
if node.left: #左
st.append(node.left)
else:
node = st.pop()
result.append(node.val)
return result
不太明白,以后再看看。/(ㄒoㄒ)/~~
总结
算法能理解,但是自己写有点难。/(ㄒoㄒ)/~~加油加油!
参考链接:https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E7%BB%9F%E4%B8%80%E8%BF%AD%E4%BB%A3%E6%B3%95.html#%E6%80%BB%E7%BB%93