树结构

main
3wish 2023-12-19 17:40:11 +08:00
parent f1355f72af
commit 43b19abc51
5 changed files with 396 additions and 0 deletions

View File

@ -0,0 +1,52 @@
"""
二叉树数组表示根据层序遍历的特性我们可以推导出父节点索引与子节点索引之间的映射公式
若某节点的索引为 i则该节点的左子节点索引为 2i + 1右子节点索引为 2i + 2
完美二叉树是一个特例在二叉树的中间层通常存在许多 None.
了解决此问题我们可以考虑在层序遍历序列中显式地写出所有 None样处理后层序遍历序列就可以唯一表示二叉树了
"""
class ArrayBinaryTree:
def __init__(self, arr: list[int | None]):
self._tree = list(arr)
def size(self):
return len(self._tree)
def val(self, i: int) -> int | None:
"""获取索引为i的节点的值"""
if i < 0 or i >= self.size():
return None
return self._tree[i]
def left(self, i: int) -> int | None:
"""获取索引为 i 节点的左子节点的索引"""
return 2 * i + 1
def right(self, i: int) -> int:
return 2 * i + 2
def parent(self, i: int) -> int:
return (i - 1) // 2
def level_order(self) -> int:
for i in self._tree:
if i:
yield i
def dfs(self, i: int, order: str):
"""深度优先遍历"""
if self.val(i) is None:
return
# 前序遍历
if order == "pre":
self.res.append(self.val(i))
self.dfs(self.left(i), order)
# 中序遍历
if order == "in":
self.res.append(self.val(i))
self.dfs(self.right(i), order)
# 后序遍历
if order == "post":
self.res.append(self.val(i))

143
tree/avl_tree.py 100644
View File

@ -0,0 +1,143 @@
"""
二叉搜素树的问题在多次删除和插入操作之后二叉搜素树可能退化为链表
平衡树仲的每一个节点的平衡因子都处于 [-1, -1] 范围内当平衡因子为-2执行右旋操作当平衡因子为2时执行左旋操作
右旋步骤
1. 右旋即失衡的节点 node 旋转向下如果其子节点只有一个左子节点 node 直接作为子节点的右子节点满足左 < <
2. 如有其子节点有两个节点 node 作为子节点的右子节点并将原右子节点作为 node 左子节点
3. 选转完后将选择之后的新根节点接回之前 node 的父节点
左旋只需镜像右旋操作即可
然而在有些时候无论左旋还是右旋都无法达到平衡这时候需要先左旋后右旋或者先右旋后左旋
1. 如果是左倾树且失衡节点的子节点的平衡因子为-1即右倾则将失衡节点的子节点先左旋再对失衡节点右旋
2. 如果是右倾树且失衡节点的子节点的平衡因子为1即左倾则将失衡节点的子节点先右旋再对失衡节点左旋
插入节点操作
插入节点操作基本和二叉查找树类似但每次插入时为了防止失衡需要从这个节点开始自底向上执行旋转操作使所有失衡节点恢复平衡
"""
from typing import Self
class TreeNode:
"""AVL 树节点类"""
def __init__(self, val: int):
self.val: int = val # 节点值
self.height: int = 0 # 节点高度,指从该节点到其最远叶节点的高度
self.left: TreeNode | None = None # 左子节点引用
self.right: TreeNode | None = None # 右子节点引用
def get_height(self, node: Self | None) -> int:
# 获取树的高度
if node is not None:
return node.height
return -1
def update_height(self, node: Self | None):
# 节点高度等于最高子树高度 + 1
node.height = max([self.get_height(node.left), self.get_height(node.right)]) + 1
def balance_factor(self, node: Self | None) -> int:
# 获取节点的平衡因子
if node is None:
return 0
# 节点平衡因子 = 左子树高度 - 右子树高度
return self.get_height(node.left) - self.get_height(node.right)
def right_rotate(self, node: Self | None) -> Self | None:
child = node.left
grand_child = child.right
# 以 child 为原点,将 node 向右旋转
child.right = node
node.left = grand_child
# 更新节点高度
self.update_height(node)
self.update_height(child)
# 返回旋转后的子树的根节点
return child
def left_rotate(self, node: Self | None) -> Self | None:
child = node.right
grand_child = child.left
# 以 child 为原点,将 node 向右旋转
child.left = node
node.right = grand_child
# 更新节点高度
self.update_height(node)
self.update_height(child)
# 返回旋转后的子树的根节点
return child
def rotate(self, node: Self | None) -> Self | None:
balance_factor = self.balance_factor(node)
# 左倾树
if balance_factor > 1:
if self.balance_factor(node.left) >= 0:
return self.right_rotate(node)
else:
# 先左旋后右旋
node.left = self.left_rotate(node.left)
return self.right_rotate(node)
# 右倾树
elif balance_factor < -1:
if self.balance_factor(node.right) <= 0:
return self.left_rotate(node)
else:
node.right = self.right_rotate(node.right)
return self.left_rotate(node)
return node
def insert(self, val):
self._root = self.insert_helper(self._root, val)
def insert_helper(self, node: Self | None, val: int) -> Self:
"""递归插入节点"""
if node is None:
# 作为根节点
return TreeNode(val)
# 查找插入位置:
if val < node.val:
node.left = self.insert_helper(node.left, val)
elif val > node.val:
node.right = self.insert_helper(node.right, val)
else:
# 重复节点不插入
return node
# 更新节点高度
self.update_height(node)
return self.rotate(node)
def remove(self, val: int):
self._root = self.remove_helper(self._root, val)
def remove_helper(self, node: Self | None, val: int) -> Self:
if node is None:
return None
if val < node.val:
node.left = self.remove_helper(node.left, val)
elif val > node.val:
node.right = self.remove_helper(node.right, val)
else:
if node.left is None or node.right is None:
child = node.left or node.right
if child is None:
return None
else:
node = child
else:
temp = node.right
while temp.left is not None:
temp = temp.left
node.right = self.remove_helper(node.right, temp.val)
node.val = temp.val
self.update_height(node)
return self.rotate(node)

View File

@ -0,0 +1,123 @@
"""
二叉搜素树满足一下条件
1. 对于根节点左子树中所有节点的值 小于 根节点的值 小于 右子树中所有节点的值
2. 任意节点的左右子树也是二叉搜索树即同样满足条件 1.
查找节点
1. 从根节点查找循环比较节点值和要查找的值大小
2. 若节点值小于查找值则走右子树
3. 若节点值大于查找值则走左子树
"""
from typing import Self
class BinarySearchTree:
def __init__(self, val: int = None, key: int = None):
self.val: int | None = val
self.key: int | None = key
self.left: BinarySearchTree | None = None
self.right: BinarySearchTree | None = None
def search_val(self, key: int) -> int | None:
if self.key is None:
return None
cur = self
while cur:
if cur.key == key:
return cur.val
if cur.key > key:
cur = cur.left
else:
cur = cur.right
return None
def insert(self, key: int, val: int):
if self.val is None:
self.key = key
self.val = val
return
cur, pre = self, None
while cur:
if cur.key == key:
cur.val = val
return
if cur.key > key:
if cur.left is None:
cur.left = BinarySearchTree(key, val)
return
else:
cur, pre = cur.left, cur
else:
if cur.right is None:
cur.right = BinarySearchTree(key, val)
return
else:
cur, pre = cur.right, cur
if pre.key < key:
pre.right = BinarySearchTree(key, val)
else:
pre.left = BinarySearchTree(key, val)
def remove(self, key: int) -> int | None:
if self.key is None:
return None
cur, pre = self, None
while cur:
if cur.key == key:
break
if cur.key > key:
cur, pre = cur.left, cur
else:
cur, pre = cur.right, cur
if cur is None:
# 没有找到要删除的节点
return
if cur.left is None or cur.right is None:
# 子节点数量有 0 个或 1 个
child: BinarySearchTree | None = cur.left or cur.right
if cur.key != self.key:
# 不是根节点
if pre.left.key == cur.key:
# 查找到的当前节点是前一个节点的左子节点
pre.left = child
else:
pre.right = child
else:
if child:
self.key = child.key
self.val = child.val
else:
self.key = None
self.val = None
else:
# 子节点数量有两个, 无法直接删除它,而需要使用一个节点替换该节点
# 由于要保持二叉搜索树“左子树 < 根节点 < 右子树”的性质,
# 因此这个节点可以是右子树的最小节点或左子树的最大节点。
temp, pre1 = cur.right, cur
# 找右子树的最小节点
while temp.left:
temp, pre1 = temp.left, temp
if pre.left.key == cur.key:
pre.left = temp
pre1.left = None
else:
pre.right = temp
pre1.left = None
def inorder(self, bst: Self):
# 中序遍历的结果是升序
if bst.left:
self.inorder(self.left)
print(self.val)
if bst.right:
self.inorder(self.right)

View File

@ -0,0 +1,30 @@
"""
二叉树常见术语
- 根节点 root node位于二叉树顶层的节点没有父节点
- 叶节点 leaf node没有子节点的节点其两个指针均指向
- edge连接两个节点的线段即节点引用指针
- 节点所在的 level从顶至底递增根节点所在层为 1
- 节点的 degree节点的子节点的数量在二叉树中度的取值范围是 012
- 二叉树的高度 height从根节点到最远叶节点所经过的边的数量
- 节点的深度 depth从根节点到该节点所经过的边的数量
- 节点的高度 height从距离该节点最远的叶节点到该节点所经过的边的数量
"""
class TreeNode:
def __init__(self, val: int):
self.val = val
self.left: TreeNode | None = None
self.right: TreeNode | None = None
"""
# 完美二叉树:除了叶节点,所有节点都有两个子节点,且左右节点数相同,
若树的高度为h则节点数为 2^(h+1)-1也称为满二叉树
# 完全二叉树:只有最底层节点未被填满,且填充顺序从左到右
# 完满
"""

48
tree/tree_order.py 100644
View File

@ -0,0 +1,48 @@
from .binary_tree import TreeNode
"""
二叉树的遍历层序遍历一层一层的遍历方向为从左到右或从右到左
"""
def level_order(root: TreeNode):
q = [root]
res = []
while q:
node = q.pop(0)
res.append(node.val)
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
return res
"""
二叉树遍历前序中序后序遍历都属于深度优先遍历 depth-first traversal, DFS
前序先遍历根节点再遍历左节点再遍历右节点
中续先遍历左节点再遍历根节点再遍历右节点
后续先左再右后中
"""
def pre_order(root: TreeNode):
# 前序遍历
print(root.val)
if root.left:
pre_order(root.left)
if root.right:
pre_order(root.right)
def in_order(root: TreeNode):
in_order(root.left)
print(root.val)
in_order(root.right)
def post_order(root: TreeNode):
post_order(root.left)
post_order(root.right)
print(root.val)