目录
二叉树
1. 理论基础
1.1 二叉树基础
二叉树 一种特殊的树形数据结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。以下是一些相关概念:
- **节点: ** 在二叉树中,除了根节点之外,每个节点都有且仅有一个父节点。
- 左子节点和右子节点: 每个节点可以有一个左子节点和一个右子节点。
- 根节点: 二叉树中没有父节点的节点称为根节点,它是树的起始点。
- 层数: 根节点的层数为1,它的子节点层数为2,依此类推。
- **深度和高度:**二叉树中节点的深度是该节点到根节点的边的数量看,其中根节点的深度是1。二叉树的高度是从根节点到最远叶子节点的最长路径上的节点数。
- 子树: 一棵树的根节点和它所有后代节点组成的树成为该节点的子树。
1.2 特殊二叉树
- 完全二叉树: 二叉树的每个节点都尽可能地填满左边的子节点,这样的二叉树称为完全二叉树。堆 就是一颗完全二叉树,同时保证父子节点的顺序关系。
- 满二叉树: 二叉树的所有层都被完全填满,除了可能的最后一层,且最后一层的所有节点都尽可能地靠左排列,这样的二叉树称为满二叉树。
- 平衡二叉树: 左右子树的高度差不超过1的二叉树,是一种特殊的二叉搜索树,可以保证操作的效率。
- 二叉搜索树: 是一个有序树,其中每个节点的值都大于其左子树中所有节点的值,并且小于其右子树中所有节点的值。
- 二叉堆:
- 大顶堆: 每个节点的键值都大于或等于其子节点的键值。
- 小顶堆: 每个节点的键值都小俞或等于其子节点的键值。
- 霍夫曼树: 用于数据压缩的二叉树,它根据字符出现的概率来构造,使得预期编码长度最小
1.3 二叉树的存储方式
- 链式存储: 指针存储,通过指针把分布在各个地址的节点串联一起。
- 顺序存储: 数组存储,在内存是连续分布的。
1.4 二叉树的遍历方式
- 深度优先遍历(Depth-First Search, DFS): 先往深走,遇到叶子节点再往回走。
- 前序遍历: (递归法,迭代法)
- 中序遍历: (递归法,迭代法)
- 后序遍历: (递归法,迭代法)
△:这里前中后,指的是中间节点的遍历顺序。
- 广度优先遍历(Breadth-First Search, BFS): 一层一层的去遍历。
- 层次遍历: (迭代法)
- 层次遍历: (迭代法)
1.5 二叉树的python实现
class TreeNode:
def __init_(self, val, left = None, right = None):
self.val = val
self.left = left
self.right = right
在leetcode中,往往只给出了上述的类来构建二叉树的节点结构,平台会自动处理输入的解析,通常无需用户手动编写构建树的函数,用户只需关注如何实现算法。在这里我们也给出基于上述的节点结构,我们如何构造一个二叉树:
def build_tree(nodes):
if not nodes:
return
root = TreeNode(nodes[0])
queue = [root]
index = 1
while index < len(nodes):
node = queue.pop(0)
if index < len(nodes) and nodes[index] is not None:
node.left = TreeNode(nodes[index])
queue.append(node.left)
index += 1
if index < len(nodes) and nodes[index] is not None:
node.right = TreeNode(nodes[index])
queue.append(node.right)
index += 1
return root
2.相关题目
二叉树的递归遍历
题目描述: 分别写出二叉树的前序、中序、后序遍历。
思路: 递归算法的三要素
(1)确定递归函数的参数和返回值
(2)确定终止条件
(3)确定单层递归的逻辑
1. 前序遍历
# 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) :
def dfs(node): # 递归函数dfs,深度优先搜索。
if node is None:
return
res.append(node.val) # 先存储根节点
dfs(node.left) # 递归调用dfs,遍历左子树
dfs(node.right)
res = [] # 用于存储前序遍历的结果
dfs(root) # 调用定义好的dfs函数
return res
2. 中序遍历
# 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: Optional[TreeNode]) -> List[int]:
def dfs(node):
if node is None:
return
dfs(node.left)
res.append(node.val)
dfs(node.right)
res = []
dfs(root)
return res
2. 后序遍历
# 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: Optional[TreeNode]) -> List[int]:
def dfs(node):
if node is None:
return
dfs(node.left)
dfs(node.right)
res.append(node.val)
res = []
dfs(root)
return res
二叉树的迭代遍历
题目描述: 采用迭代法分别实现前中后序遍历
思路: 在栈与队列中剃刀栈与递归的相似性,这里可以采用栈来实现二叉树的前中后序遍历。
(1)前序遍历:每次先处理中间节点,先将根节点放入栈中,再将右子节点放入栈中,最后放入左子节点。这样能确保出栈的顺序是中左右。注意这里前序遍历访问节点的顺序,与处理节点的顺序是一致的
(2)中序遍历:与前序遍历不同,在中序遍历中,处理顺序和访问顺序不一致,先访问的是二叉树顶部的节点,再逐层访问,直到到达树左面的最底部。再开始处理节点。
(3)后序遍历:调整前序遍历的代码即可。
1. 前序遍历
# 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) :
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
2. 中序遍历
# 假设 TreeNode 类已经定义如下:
# 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: Optional[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
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) :
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
题目描述:
思路:
在这里插入代码片
二叉树的层序遍历
二叉树的层序遍历
题目描述: 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
思路: 传建一个队列,将根节点入队。当队列非空时,进行循环;每次循环中,创建一个列表level来存储当前层的节点值。取值队列前端的节点,将其值添加到当前层级列表中。将取出节点的左子节点和右子节点添加到队列末尾。将当前层级列表添加到结果列表中。
from collections import deque
# 定义二叉树节点类
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]]:
# 如果根节点为空,直接返回空列表
if not root:
return []
# 使用双端队列来存储待处理的节点
queue = 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
二叉树的层序遍历(二)
题目描述: 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
思路: 将上述的结果取反。return result[::-1]
199.二叉树的右视图
题目描述: 给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
思路: 层序遍历时,判断是否遍历到单层的最后面的元素,如果是,就放进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: Optional[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
429.N叉树的层序遍
题目描述: 给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
思路: 相比上面的模板题,两个节点增加为多个子节点
"""
# Definition for a Node.
class Node:
def __init__(self, val=None, children=None):
self.val = val
self.children = children
"""
class Solution:
def levelOrder(self, root: 'Node') -> List[List[int]]:
if not root:
return []
result = []
queue = collections.deque([root])
while queue:
level_size = len(queue)
level = []
for _ in range(level_size):
node = queue.popleft()
level.append(node.val)
for child in node.children:
queue.append(child)
result.append(level)
return result
515.在每个树行中找最大值
题目描述: 您需要在二叉树的每一行中找到最大的值。
思路: 先设置一个最大值 max_val = float('-inf')
,然后再媒体弹出节点是,进行比较 max_val = max(max_val, node.val)
116.填充每个节点的下一个右侧节点指针
题目描述: 给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。初始状态下,所有 next 指针都被设置为 NULL。
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
思路: 使用层序遍历发,每一层中,采用 prev.next = node
,注意,这里是指针指向下一个节点,而不是赋值。然后再进行更新,prev = node
# 定义一个节点类Node,每个节点包含值val、左子节点left、右子节点right和一个指向下一层同位置节点的指针next。
class Node:
def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None):
self.val = val # 节点存储的值
self.left = left # 左子节点
self.right = right # 右子节点
self.next = next # 指向同一层的下一个节点
# 定义一个解决方案类Solution,包含一个方法connect,用于连接二叉树的节点。
class Solution:
def connect(self, root: 'Node') -> 'Node':
# 如果根节点为空,直接返回None
if not root:
return root
# 使用队列来存储每一层的节点
queue = collections.deque([root])
# 当队列不为空时,继续处理
while queue:
# 记录当前层的节点数量
level_size = len(queue)
prev = None # 初始化前一个节点为None
# 遍历当前层的所有节点
for i in range(level_size):
# 从队列中弹出一个节点
node = queue.popleft()
# 如果前一个节点不为空,将当前节点的next指针指向前一个节点
if prev:
prev.next = node
# 更新前一个节点为当前节点
prev = node
# 如果当前节点有左子节点,将其加入队列,以便下一层处理
if node.left:
queue.append(node.left)
# 如果当前节点有右子节点,将其加入队列,以便下一层处理
if node.right:
queue.append(node.right)
# 返回连接后的树的根节点
return root
104.二叉树的最大深度
题目描述: 给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明: 叶子节点是指没有子节点的节点。
思路: 层序遍历法,求其层数
111.二叉树的最小深度
题目描述: 给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明: 叶子节点是指没有子节点的节点。
思路: 只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点。下面给出核心代码
if not node.left and not node.right:
return depth
翻转二叉树
题目描述: 翻转一颗二叉树
思路: 实际上是做一个镜像对称,将每个节点的左右子节点翻转,翻转二叉树必须要遍历,前序、后序遍历都可以,中序不可以,因为先左子树,再翻转root左右子树,这时的右子树其实是之前的左子树,最后翻转此时的右子树其实是翻转之前的左子树,这样左子树被翻转了两次。
1. 前序遍历:
# 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 invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
root.left, root.right = root.right, root.left
self.invertTree(root.left)
self.invertTree(root.right)
return root
2. (伪)中序遍历: 这里为了解决前面提到的两次翻转同一个节点的问题,固每次迭代root.left
# 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 invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
self.invertTree(root.left)
root.left, root.right = root.right, root.left
self.invertTree(root.left)
return root
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 invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
self.invertTree(root.left)
self.invertTree(root.right)
root.left, root.right = root.right, root.left
return root
4. 层序遍历法
# 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 invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
queue = collections.deque([root])
while queue:
for i in range(len(queue)):
node = queue.popleft()
node.left, node.right = node.right, node.left
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
return root