From 43b19abc510ee43500422d3e6107f0a785bc1c19 Mon Sep 17 00:00:00 2001 From: 3wish Date: Tue, 19 Dec 2023 17:40:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=91=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tree/array_bin_tree.py | 52 +++++++++++++++ tree/avl_tree.py | 143 ++++++++++++++++++++++++++++++++++++++++ tree/bin_search_tree.py | 123 ++++++++++++++++++++++++++++++++++ tree/binary_tree.py | 30 +++++++++ tree/tree_order.py | 48 ++++++++++++++ 5 files changed, 396 insertions(+) create mode 100644 tree/array_bin_tree.py create mode 100644 tree/avl_tree.py create mode 100644 tree/bin_search_tree.py create mode 100644 tree/binary_tree.py create mode 100644 tree/tree_order.py diff --git a/tree/array_bin_tree.py b/tree/array_bin_tree.py new file mode 100644 index 0000000..3c6de01 --- /dev/null +++ b/tree/array_bin_tree.py @@ -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)) diff --git a/tree/avl_tree.py b/tree/avl_tree.py new file mode 100644 index 0000000..64da100 --- /dev/null +++ b/tree/avl_tree.py @@ -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) \ No newline at end of file diff --git a/tree/bin_search_tree.py b/tree/bin_search_tree.py new file mode 100644 index 0000000..16579db --- /dev/null +++ b/tree/bin_search_tree.py @@ -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) diff --git a/tree/binary_tree.py b/tree/binary_tree.py new file mode 100644 index 0000000..807d179 --- /dev/null +++ b/tree/binary_tree.py @@ -0,0 +1,30 @@ +""" +二叉树常见术语: + +- 「根节点 root node」:位于二叉树顶层的节点,没有父节点。 +- 「叶节点 leaf node」:没有子节点的节点,其两个指针均指向。 +- 「边 edge」:连接两个节点的线段,即节点引用(指针)。 +- 节点所在的「层 level」:从顶至底递增,根节点所在层为 1 。 +- 节点的「度 degree」:节点的子节点的数量。在二叉树中,度的取值范围是 0、1、2 。 +- 二叉树的「高度 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。也称为满二叉树 + +# 完全二叉树:只有最底层节点未被填满,且填充顺序从左到右 + +# 完满 +""" + diff --git a/tree/tree_order.py b/tree/tree_order.py new file mode 100644 index 0000000..4e5d491 --- /dev/null +++ b/tree/tree_order.py @@ -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) \ No newline at end of file