Hello算法7:二叉树
https://www.hello-algo.com/chapter_tree/binary_tree/
基本介绍
二叉树是一种非线性数据结构,代表祖先和后辈的关系,体现了分治思想。
二叉树的基本单元节点,包含值,左节点和右节点的引用
class TreeNode:
"""二叉树节点类"""
def __init__(self, val: int):
self.val: int = val # 节点值
self.left: Optional[TreeNode] = None # 左子节点引用
self.right: Optional[TreeNode] = None # 右子节点引用
常见定义
- 根节点-root node-位于顶部的节点,没有父节点
- 叶节点-leaf node-位于底部的节点,没有子节点
- 边-dege-连接两个节点的线段,也就是指针
- 节点所在的层-level-,根节点的层是1,从顶至底递增
- 节点的度-degree-节点的子节点的数量。有0,1,2个子节点
- 二叉树的高度:从根节点到最远叶节点所经过的边的数量
- 节点的深度:从根节点到该节点,所经过的边的数量
- 节点的高度:从最远叶节点到该节点,所经过的边的数量
初始化二叉树
# 初始化二叉树
# 初始化节点
n1 = TreeNode(val=1)
n2 = TreeNode(val=2)
n3 = TreeNode(val=3)
n4 = TreeNode(val=4)
n5 = TreeNode(val=5)
# 构建引用,即指针
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
插入或删除节点
# 插入与删除节点
p = TreeNode(0)
# 在 n1 -> n2 中间插入节点 P
n1.left = p
p.left = n2
# 删除节点 P
n1.left = n2
常见二叉树
-
完美二叉树 perfect
所有节点的度都是2,也就是所有节点都有2个子节点的二叉树。完美反映细胞分裂。
-
完全二叉树 complet
只有最底层节点未填满,且节点靠左填充
-
完满二叉树 full
除了叶节点之外,所有节点都有2个子节点
-
平衡二叉树 balanced
任意节点的左子树和右子树的高度差,绝对值不超过1
层序遍历
从顶部到底部,逐层,从左到右进行遍历
# 层序遍历
def level_order(root: TreeNode | None) -> list[int]:
queue = deque()
queue.append(root)
res = []
while queue:
node: TreeNode = queue.popleft()
res.append(node.val)
if node.left is not None:
queue.append(node.left)
if node.right is not None:
queue.append(node.right)
return res
前序遍历
def pre_order(root: TreeNode | None):
if root is None:
return
res.append(root.val)
pre_order(root=root.left)
pre_order(root=root.right)
二叉树数组表示
先分析一种简单的情况,完美二叉树,左节点是2i+1,右节点是2i+2,这时候可以很方便的用数组表示;
再扩展到任意二叉树,用none表示和填充数组即可;
完全二叉树,none只在最底层并且靠右,很适合用数组表示;
代码如下:
略
数组表示的优点:
- 数组存储在连续的内存空间,对缓存友好
- 不需要指针,节省空间
- 允许随机访问节点
数组表示的缺点:
由于需要连续内存空间,不适合存储过大的树
增删节点需要通过数组的插入和删除来实现,效率较低
当二叉树中存在大量None时,空间利用率较低
二叉搜索树
定义:
- 对于根节点,左子树中所有节点的值<根节点的值<右子树中所有节点的值
- 任意子树,同样满足上一个条件
搜索操作:
def search(self, num: int) -> TreeNode | None:
"""查找节点"""
cur = self._root
# 循环查找,越过叶节点后跳出
while cur is not None:
# 目标节点在 cur 的右子树中
if cur.val < num:
cur = cur.right
# 目标节点在 cur 的左子树中
elif cur.val > num:
cur = cur.left
# 找到目标节点,跳出循环
else:
break
return cur
插入操作:
为了保证插入元素后依然满足二叉搜索树的性质,插入步骤如下
- 查找插入位置,类似于搜索操作
- 在该位置插入节点
def insert(self, num: int):
"""插入节点"""
# 若树为空,则初始化根节点,也就是直接插在根节点
if self._root is None:
self._root = TreeNode(num)
return
# 循环插找
cur, pre = self._root, None
while cur is not None:
# 找到重复值,直接return
if cur.val == num:
return
pre = cur
if cur.val < num:
cur = cur.right
else:
cur = cur.left
# 插入节点
node = TreeNode(num)
if pre.val < num:
pre.right = node
else:
pre.left = node
删除操作:
查找到要删除的节点后,删除分为三种情况
是叶节点,直接删除即可
有1个子节点,用子节点替换该节点