Merge branch 'master' into heap-dev

This commit is contained in:
Yudong Jin
2023-01-12 04:11:22 +08:00
committed by GitHub
118 changed files with 3386 additions and 1014 deletions
+170 -17
View File
@@ -60,7 +60,13 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
=== "Go"
```go title="avl_tree.go"
/* AVL 树结点类 */
type TreeNode struct {
Val int // 结点值
Height int // 结点高度
Left *TreeNode // 左子结点引用
Right *TreeNode // 右子结点引用
}
```
=== "JavaScript"
@@ -100,7 +106,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
```
「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1** 。我们封装两个工具函数,分别用于获取与更新结点的高度。
「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1**。我们封装两个工具函数,分别用于获取与更新结点的高度。
=== "Java"
@@ -128,14 +134,14 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
```python title="avl_tree.py"
""" 获取结点高度 """
def height(self, node: typing.Optional[TreeNode]) -> int:
def height(self, node: Optional[TreeNode]) -> int:
# 空结点高度为 -1 ,叶结点高度为 0
if node is not None:
return node.height
return -1
""" 更新结点高度 """
def __update_height(self, node: TreeNode):
def __update_height(self, node: Optional[TreeNode]):
# 结点高度等于最高子树高度 + 1
node.height = max([self.height(node.left), self.height(node.right)]) + 1
```
@@ -143,7 +149,26 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
=== "Go"
```go title="avl_tree.go"
/* 获取结点高度 */
func height(node *TreeNode) int {
// 空结点高度为 -1 ,叶结点高度为 0
if node != nil {
return node.Height
}
return -1
}
/* 更新结点高度 */
func updateHeight(node *TreeNode) {
lh := height(node.Left)
rh := height(node.Right)
// 结点高度等于最高子树高度 + 1
if lh > rh {
node.Height = lh + 1
} else {
node.Height = rh + 1
}
}
```
=== "JavaScript"
@@ -214,7 +239,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
```python title="avl_tree.py"
""" 获取平衡因子 """
def balance_factor(self, node: TreeNode) -> int:
def balance_factor(self, node: Optional[TreeNode]) -> int:
# 空结点平衡因子为 0
if node is None:
return 0
@@ -225,7 +250,15 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
=== "Go"
```go title="avl_tree.go"
/* 获取平衡因子 */
func balanceFactor(node *TreeNode) int {
// 空结点平衡因子为 0
if node == nil {
return 0
}
// 结点平衡因子 = 左子树高度 - 右子树高度
return height(node.Left) - height(node.Right)
}
```
=== "JavaScript"
@@ -277,7 +310,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
### Case 1 - 右旋
如下图所示(结点下方为「平衡因子」),从底至顶看,二叉树中首个失衡结点是 **结点 3** 。我们聚焦在以该失衡结点为根结点的子树上,将该结点记为 `node` ,将其左子节点记为 `child` ,执行「右旋」操作。完成右旋后,该子树已经恢复平衡,并且仍然为二叉搜索树。
如下图所示(结点下方为「平衡因子」),从底至顶看,二叉树中首个失衡结点是 **结点 3**。我们聚焦在以该失衡结点为根结点的子树上,将该结点记为 `node` ,将其左子节点记为 `child` ,执行「右旋」操作。完成右旋后,该子树已经恢复平衡,并且仍然为二叉搜索树。
=== "Step 1"
![right_rotate_step1](avl_tree.assets/right_rotate_step1.png)
@@ -322,7 +355,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
```python title="avl_tree.py"
""" 右旋操作 """
def __right_rotate(self, node: TreeNode) -> TreeNode:
def __right_rotate(self, node: Optional[TreeNode]) -> TreeNode:
child = node.left
grand_child = child.right
# 以 child 为原点,将 node 向右旋转
@@ -338,7 +371,19 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
=== "Go"
```go title="avl_tree.go"
/* 右旋操作 */
func rightRotate(node *TreeNode) *TreeNode {
child := node.Left
grandChild := child.Right
// 以 child 为原点,将 node 向右旋转
child.Right = node
node.Left = grandChild
// 更新结点高度
updateHeight(node)
updateHeight(child)
// 返回旋转后子树的根节点
return child
}
```
=== "JavaScript"
@@ -425,7 +470,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
```python title="avl_tree.py"
""" 左旋操作 """
def __left_rotate(self, node: TreeNode) -> TreeNode:
def __left_rotate(self, node: Optional[TreeNode]) -> TreeNode:
child = node.right
grand_child = child.left
# 以 child 为原点,将 node 向左旋转
@@ -441,7 +486,19 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
=== "Go"
```go title="avl_tree.go"
/* 左旋操作 */
func leftRotate(node *TreeNode) *TreeNode {
child := node.Right
grandChild := child.Left
// 以 child 为原点,将 node 向左旋转
child.Left = node
node.Right = grandChild
// 更新结点高度
updateHeight(node)
updateHeight(child)
// 返回旋转后子树的根节点
return child
}
```
=== "JavaScript"
@@ -564,7 +621,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
```python title="avl_tree.py"
""" 执行旋转操作,使该子树重新恢复平衡 """
def __rotate(self, node: TreeNode) -> TreeNode:
def __rotate(self, node: Optional[TreeNode]) -> TreeNode:
# 获取结点 node 的平衡因子
balance_factor = self.balance_factor(node)
# 左偏树
@@ -592,7 +649,36 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
=== "Go"
```go title="avl_tree.go"
/* 执行旋转操作,使该子树重新恢复平衡 */
func rotate(node *TreeNode) *TreeNode {
// 获取结点 node 的平衡因子
// Go 推荐短变量,这里 bf 指代 balanceFactor
bf := balanceFactor(node)
// 左偏树
if bf > 1 {
if balanceFactor(node.Left) >= 0 {
// 右旋
return rightRotate(node)
} else {
// 先左旋后右旋
node.Left = leftRotate(node.Left)
return rightRotate(node)
}
}
// 右偏树
if bf < -1 {
if balanceFactor(node.Right) <= 0 {
// 左旋
return leftRotate(node)
} else {
// 先右旋后左旋
node.Right = rightRotate(node.Right)
return leftRotate(node)
}
}
// 平衡树,无需旋转,直接返回
return node
}
```
=== "JavaScript"
@@ -710,7 +796,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
return self.root
""" 递归插入结点(辅助函数)"""
def __insert_helper(self, node: typing.Optional[TreeNode], val: int) -> TreeNode:
def __insert_helper(self, node: Optional[TreeNode], val: int) -> TreeNode:
if node is None:
return TreeNode(val)
# 1. 查找插入位置,并插入结点
@@ -730,7 +816,32 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
=== "Go"
```go title="avl_tree.go"
/* 插入结点 */
func (t *avlTree) insert(val int) *TreeNode {
t.root = insertHelper(t.root, val)
return t.root
}
/* 递归插入结点(辅助函数) */
func insertHelper(node *TreeNode, val int) *TreeNode {
if node == nil {
return NewTreeNode(val)
}
/* 1. 查找插入位置,并插入结点 */
if val < node.Val {
node.Left = insertHelper(node.Left, val)
} else if val > node.Val {
node.Right = insertHelper(node.Right, val)
} else {
// 重复结点不插入,直接返回
return node
}
// 更新结点高度
updateHeight(node)
/* 2. 执行旋转操作,使该子树重新恢复平衡 */
node = rotate(node)
// 返回子树的根节点
return node
}
```
=== "JavaScript"
@@ -846,7 +957,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
return root
""" 递归删除结点(辅助函数) """
def __remove_helper(self, node: typing.Optional[TreeNode], val: int) -> typing.Optional[TreeNode]:
def __remove_helper(self, node: Optional[TreeNode], val: int) -> Optional[TreeNode]:
if node is None:
return None
# 1. 查找结点,并删除之
@@ -876,7 +987,49 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
=== "Go"
```go title="avl_tree.go"
/* 删除结点 */
func (t *avlTree) remove(val int) *TreeNode {
root := removeHelper(t.root, val)
return root
}
/* 递归删除结点(辅助函数) */
func removeHelper(node *TreeNode, val int) *TreeNode {
if node == nil {
return nil
}
/* 1. 查找结点,并删除之 */
if val < node.Val {
node.Left = removeHelper(node.Left, val)
} else if val > node.Val {
node.Right = removeHelper(node.Right, val)
} else {
if node.Left == nil || node.Right == nil {
child := node.Left
if node.Right != nil {
child = node.Right
}
// 子结点数量 = 0 ,直接删除 node 并返回
if child == nil {
return nil
} else {
// 子结点数量 = 1 ,直接删除 node
node = child
}
} else {
// 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点
temp := getInOrderNext(node.Right)
node.Right = removeHelper(node.Right, temp.Val)
node.Val = temp.Val
}
}
// 更新结点高度
updateHeight(node)
/* 2. 执行旋转操作,使该子树重新恢复平衡 */
node = rotate(node)
// 返回子树的根节点
return node
}
```
=== "JavaScript"
+21 -21
View File
@@ -83,7 +83,7 @@ comments: true
```python title="binary_search_tree.py"
""" 查找结点 """
def search(self, num: int) -> typing.Optional[TreeNode]:
def search(self, num: int) -> Optional[TreeNode]:
cur = self.root
# 循环查找,越过叶结点后跳出
while cur is not None:
@@ -103,7 +103,7 @@ comments: true
```go title="binary_search_tree.go"
/* 查找结点 */
func (bst *BinarySearchTree) Search(num int) *TreeNode {
func (bst *binarySearchTree) search(num int) *TreeNode {
node := bst.root
// 循环查找,越过叶结点后跳出
for node != nil {
@@ -202,8 +202,8 @@ comments: true
给定一个待插入元素 `num` ,为了保持二叉搜索树“左子树 < 根结点 < 右子树”的性质,插入操作分为两步:
1. **查找插入位置** 与查找操作类似,我们从根结点出发,根据当前结点值和 `num` 的大小关系循环向下搜索,直到越过叶结点(遍历到 $\text{null}$ )时跳出循环;
2. **在该位置插入结点** 初始化结点 `num` ,将该结点放到 $\text{null}$ 的位置
1. **查找插入位置**与查找操作类似,我们从根结点出发,根据当前结点值和 `num` 的大小关系循环向下搜索,直到越过叶结点(遍历到 $\text{null}$ )时跳出循环;
2. **在该位置插入结点**初始化结点 `num` ,将该结点放到 $\text{null}$ 的位置
二叉搜索树不允许存在重复结点,否则将会违背其定义。因此若待插入结点在树中已经存在,则不执行插入,直接返回即可。
@@ -265,7 +265,7 @@ comments: true
```python title="binary_search_tree.py"
""" 插入结点 """
def insert(self, num: int) -> typing.Optional[TreeNode]:
def insert(self, num: int) -> Optional[TreeNode]:
root = self.root
# 若树为空,直接提前返回
if root is None:
@@ -299,7 +299,7 @@ comments: true
```go title="binary_search_tree.go"
/* 插入结点 */
func (bst *BinarySearchTree) Insert(num int) *TreeNode {
func (bst *binarySearchTree) insert(num int) *TreeNode {
cur := bst.root
// 若树为空,直接提前返回
if cur == nil {
@@ -442,15 +442,15 @@ comments: true
与插入结点一样,我们需要在删除操作后维持二叉搜索树的“左子树 < 根结点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除结点。接下来,根据待删除结点的子结点数量,删除操作需要分为三种情况:
**待删除结点的子结点数量 $= 0$ **表明待删除结点是叶结点,直接删除即可。
**待删除结点的子结点数量 $= 0$ **表明待删除结点是叶结点,直接删除即可。
![bst_remove_case1](binary_search_tree.assets/bst_remove_case1.png)
**待删除结点的子结点数量 $= 1$ **将待删除结点替换为其子结点。
**待删除结点的子结点数量 $= 1$ **将待删除结点替换为其子结点即可
![bst_remove_case2](binary_search_tree.assets/bst_remove_case2.png)
**待删除结点的子结点数量 $= 2$ **删除操作分为三步:
**待删除结点的子结点数量 $= 2$ **删除操作分为三步:
1. 找到待删除结点在 **中序遍历序列** 中的下一个结点,记为 `nex`
2. 在树中递归删除结点 `nex`
@@ -560,7 +560,7 @@ comments: true
```python title="binary_search_tree.py"
""" 删除结点 """
def remove(self, num: int) -> typing.Optional[TreeNode]:
def remove(self, num: int) -> Optional[TreeNode]:
root = self.root
# 若树为空,直接提前返回
if root is None:
@@ -609,7 +609,7 @@ comments: true
```go title="binary_search_tree.go"
/* 删除结点 */
func (bst *BinarySearchTree) Remove(num int) *TreeNode {
func (bst *binarySearchTree) remove(num int) *TreeNode {
cur := bst.root
// 若树为空,直接提前返回
if cur == nil {
@@ -653,10 +653,10 @@ comments: true
// 子结点数为 2
} else {
// 获取中序遍历中待删除结点 cur 的下一个结点
next := bst.GetInOrderNext(cur)
next := bst.getInOrderNext(cur)
temp := next.Val
// 递归删除结点 next
bst.Remove(next.Val)
bst.remove(next.Val)
// 将 next 的值复制给 cur
cur.Val = temp
}
@@ -830,17 +830,17 @@ comments: true
假设给定 $n$ 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为:
- **查找元素** 由于数组是无序的,因此需要遍历数组来确定,使用 $O(n)$ 时间;
- **插入元素** 只需将元素添加至数组尾部即可,使用 $O(1)$ 时间;
- **删除元素** 先查找元素,使用 $O(n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间;
- **获取最小 / 最大元素** 需要遍历数组来确定,使用 $O(n)$ 时间;
- **查找元素**由于数组是无序的,因此需要遍历数组来确定,使用 $O(n)$ 时间;
- **插入元素**只需将元素添加至数组尾部即可,使用 $O(1)$ 时间;
- **删除元素**先查找元素,使用 $O(n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间;
- **获取最小 / 最大元素**需要遍历数组来确定,使用 $O(n)$ 时间;
为了得到先验信息,我们也可以预先将数组元素进行排序,得到一个「排序数组」,此时操作效率为:
- **查找元素** 由于数组已排序,可以使用二分查找,平均使用 $O(\log n)$ 时间;
- **插入元素** 先查找插入位置,使用 $O(\log n)$ 时间,再插入到指定位置,使用 $O(n)$ 时间;
- **删除元素** 先查找元素,使用 $O(\log n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间;
- **获取最小 / 最大元素** 数组头部和尾部元素即是最小和最大元素,使用 $O(1)$ 时间;
- **查找元素**由于数组已排序,可以使用二分查找,平均使用 $O(\log n)$ 时间;
- **插入元素**先查找插入位置,使用 $O(\log n)$ 时间,再插入到指定位置,使用 $O(n)$ 时间;
- **删除元素**先查找元素,使用 $O(\log n)$ 时间,再在数组中删除该元素,使用 $O(n)$ 时间;
- **获取最小 / 最大元素**数组头部和尾部元素即是最小和最大元素,使用 $O(1)$ 时间;
观察发现,无序数组和有序数组中的各项操作的时间复杂度是“偏科”的,即有的快有的慢;**而二叉搜索树的各项操作的时间复杂度都是对数阶,在数据量 $n$ 很大时有巨大优势**。
+1 -1
View File
@@ -114,7 +114,7 @@ comments: true
结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」,并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点,将左子结点以下的树称为该结点的「左子树 Left Subtree」,右子树同理。
除了叶结点外,每个结点都有子结点和子树。例如,若将上图的「结点 2」看作父结点,那么其左子结点和右子结点分别为「结点 4」和「结点 5」,左子树和右子树分别为「结点 4 以下的树」和「结点 5 以下的树」。
除了叶结点外,每个结点都有子结点和子树。例如,若将上图的「结点 2」看作父结点,那么其左子结点和右子结点分别为「结点 4」和「结点 5」,左子树和右子树分别为「结点 4 及其以下结点形成的树」和「结点 5 及其以下结点形成的树」。
![binary_tree_definition](binary_tree.assets/binary_tree_definition.png)
+4 -4
View File
@@ -66,7 +66,7 @@ comments: true
```python title="binary_tree_bfs.py"
""" 层序遍历 """
def hier_order(root: TreeNode):
def hier_order(root: Optional[TreeNode]):
# 初始化队列,加入根结点
queue = collections.deque()
queue.append(root)
@@ -277,7 +277,7 @@ comments: true
```python title="binary_tree_dfs.py"
""" 前序遍历 """
def pre_order(root: typing.Optional[TreeNode]):
def pre_order(root: Optional[TreeNode]):
if root is None:
return
# 访问优先级:根结点 -> 左子树 -> 右子树
@@ -286,7 +286,7 @@ comments: true
pre_order(root=root.right)
""" 中序遍历 """
def in_order(root: typing.Optional[TreeNode]):
def in_order(root: Optional[TreeNode]):
if root is None:
return
# 访问优先级:左子树 -> 根结点 -> 右子树
@@ -295,7 +295,7 @@ comments: true
in_order(root=root.right)
""" 后序遍历 """
def post_order(root: typing.Optional[TreeNode]):
def post_order(root: Optional[TreeNode]):
if root is None:
return
# 访问优先级:左子树 -> 右子树 -> 根结点