文章目录
一、二叉树的定义
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
二、二叉树的种类
-
满二叉树:只有度为0的结点和度为2的结点
-
完全二叉树:除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。【优先级队列】其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系。
-
二叉搜索树:二叉搜索树是有数值的了,二叉搜索树是一个有序树。若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;它的左、右子树也分别为二叉排序树
-
平衡二叉搜索树:AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
三、二叉树的存储方式
二叉树可以链式存储,也可以顺序存储。那么链式存储方式就用指针(链表), 顺序存储的方式就是用数组。顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在散落在各个地址的节点串联一起。
-
链式存储:
一般我们都是用链式存储二叉树。
-
顺序存储
如果父节点的数组下标是i,那么它的左孩子就是i * 2 + 1,右孩子就是 i * 2 + 2。
四、二叉树的遍历方式
-
深度优先遍历:先往深走,遇到叶子节点再往回走。前中后,其实指的就是中间节点的遍历顺序。实现方式:经常会使用递归的方式来实现深度优先遍历,也就是实现前中后序遍历,使用递归是比较方便的。
前序遍历(递归法,迭代法) 中序遍历(递归法,迭代法) 后序遍历(递归法,迭代法)
-
广度优先遍历:一层一层的去遍历。
层次遍历(迭代法)
(一)深度优先遍历(前中后序)
1. 二叉树的递归遍历
递归的三要素:
(1)确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
(2)确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
(3)确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
# 前序遍历-递归-LC144_二叉树的前序遍历
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
# 保存结果
result = []
def traversal(root: TreeNode):
if root == None:return
result.append(root.val) # 前序
traversal(root.left) # 左
traversal(root.right) # 右
traversal(root)
return result
# 中序遍历-递归-LC94_二叉树的中序遍历
# 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 inorderTraversal(self, root: TreeNode) -> List[int]:
# 左中右
res=[]
def traversal(root:TreeNode):
if not root:return
traversal(root.left)
res.append(root.val)
traversal(root.right)
traversal(root)
return res
# 后序遍历-递归-LC145_二叉树的后序遍历
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
result = []
def traversal(root: TreeNode):
if root == None:
return
traversal(root.left) # 左
traversal(root.right) # 右
result.append(root.val) # 后序
traversal(root)
return result
2.二叉树的迭代遍历
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。所以用栈也可以是实现二叉树的前后中序遍历了,也就用迭代法。
(1)根据迭代思路写代码
【入栈+弹出】
- 对于前序遍历(中左右),访问元素的顺序和处理元素的顺序是一致的,都是自顶向下的中间-右-左,所以操作简单。前序遍历中访问节点(遍历节点)和处理节点(将元素放进result数组中)可以同步处理,但是中序就无法做到同步。
- 对于中序遍历(左中右),先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
- 对于后序遍历(左右中),调整先序遍历(中左右)的左右代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了。
# 前序遍历-迭代-LC144_二叉树的前序遍历
class Solution:
def preorderTraversal(self, root: 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
# 中序遍历-迭代-LC94_二叉树的中序遍历
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
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
# 后序遍历-迭代-LC145_二叉树的后序遍历
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] # 将最终的数组翻转
(2)统一风格写代码
比较难理解,我也没怎么看懂这里,故1和2选择其一即可
上面的代码中,使用栈和指针来实现二叉树节点的的访问和处理,但是会发现中序遍历的写法和其他两个不一致。如果希望统一写法,可以将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。如何标记呢,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法。
【前序遍历】
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
(二)广度优先遍历(层序遍历)
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而是用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。这种层序遍历方式就是图论中的广度优先遍历。
class Solution:
"""二叉树层序遍历迭代解法"""
def levelOrder(self, root: TreeNode) -> List[List[int]]:
results = [] # 记录所有层的结果
if not root:return results
from collections import deque
que = deque([root])
while que:
size = len(que)
result = [] # 记录每一层的结果
for _ in range(size): # size就是每一层的元素个数
# 先出后进(从que中popleft,结果记录到result中,到que中append)
cur = que.popleft()
result.append(cur.val) # 中
if cur.left:que.append(cur.left) # 左
if cur.right:que.append(cur.right) # 右
results.append(result)
return results