二叉搜索树

二叉搜索树(BST:binary search/sort tree)又叫二叉查找树或者二叉排序树,它首先是一个二叉树,而且必须满足下面的条件: 1. 若左子树不空,则左子树上所有结点的值均小于它的根节点的值(左小) 2. 若右子树不空,则右子树上所有结点的值均大于它的根结点的值(右大) 3. 左、右子树也分别为二叉排序树

二叉搜索树的查找

给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。

1
2
3
4
5
6
7
8
9
10
11
12
def searchBST(root, val):
if root is None:
return []
node = root
while node != None:
if node.val == val:
return node
elif node.val > val:
node = node.left
else:
node = node.right
return None
### 二叉搜索树的节点增加

给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。可以返回 任意有效的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right

def insertIntoBST(root, val):
if root == None:
return TreeNode(val)
if root.val > val:
if root.left != None:
insertIntoBST(root.left, val)
else:
root.left = TreeNode(val)
else:
if root.right != None:
insertIntoBST(root.right, val)
else:
root.right = TreeNode(val)
return root

二叉搜索树的节点删除

在二叉搜索树中删除一个节点时需要考虑的情况: * 待删除节点为叶子节点:直接删除 * 待删除结点只有一个孩子:将其孩子移动到它的位置,将该结点删除 * 待删除结点有两个孩子:找到其中序序列的后继结点,将待删除结点更改为其中序后继结点的值,删除该后继结点

  • 递归方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    def inorderSuccessor(root, key_node):
    ## 返回结点的中序遍历的后继结点
    node = root
    pre = None
    stack = []
    while node or len(stack) > 0:
    if node:
    stack.append(node)
    node = node.left
    else:
    node = stack.pop()
    if pre == key_node:
    return node
    else:
    pre = node
    node = node.right

    def deleteNode(root, key):
    if root is None:
    return None
    node = root
    if node.val == key:
    if not node.left and not node.right:
    ## 叶子结点
    return None
    elif not node.left:
    ## 只有右孩子
    return node.right
    elif not node.right:
    ## 只有左孩子
    return node.left
    else:
    ## 左右孩子都存在
    ## 找到其中序遍历的后继结点
    successor = self.inorderSuccessor(root, node)
    ## 递归删除后继结点的值 (从当前结点出发因为后继节点值在其子树上)
    node = self.deleteNode(node, successor.val)
    node.val = successor.val
    return node
    elif node.val > key:
    node.left = self.deleteNode(node.left, key)
    else:
    node.right = self.deleteNode(node.right, key)
    return root
  • 迭代方法: (啰嗦一句:感觉自己写的迭代的代码有点....冗长,但迭代的话就得判断很多信息欸)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    def inorderSuccessor(root, key_node):
    ## 找到其中序后继结点,处理好结点后返回其值
    node = root
    stack = []
    pre = None
    while node != None or len(stack) > 0:
    if node != None:
    stack.append(node)
    node = node.left
    else:
    node = stack.pop()
    if pre == key_node:
    ## 找到中序后继结点
    val = node.val
    ## 如果后继结点存在右子树(不可能存在左子树,因为它是待删除结点的直接中序后继)
    if node.right != None:
    ## 如果该后继节点为待删除结点的右孩子
    if pre.right == node:
    pre.right = node.right
    else:
    ## 该后继节点不是待删除结点的右孩子
    ## 需要找到后继结点的父结点(栈顶结点)(父结点就是其中序遍历的后一个节点)
    pre = stack[-1]
    pre.left = node.right
    else:
    ## 如果后继结点为叶子结点
    ## 处理后继结点(删除掉它就是让其父结点指向它的变量为None)
    if pre.right == node:
    pre.right = None
    else:
    pre = stack[-1]
    pre.left = None
    return val
    pre = node
    node = node.right
    def deleteNode(root, key):
    if root is None:
    return None
    pre = root ## 记录刚刚访问过的结点
    node = root
    while node != None:
    if node.val == key:
    ## 删除
    ## 待删除节点为叶子节点:直接删除
    if node.left == None and node.right == None:
    ## 删除前判断下是否为根节点
    if node == root:
    root = None
    else:
    ## 父结点指向它的变量置空
    if pre.val > node.val:
    pre.left = None
    else:
    pre.right = None
    return root
    elif node.left == None and node.right != None:
    if node == root:
    root = node.right
    else:
    if pre.val > node.val:
    pre.left = node.right
    else:
    pre.right = node.right
    node = None
    return root
    elif node.left != None and node.right == None:
    if node == root:
    root = node.left
    else:
    if pre.val > node.val:
    pre.left = node.left
    else:
    pre.right = node.left
    node = None
    return root
    else:
    node_successor_val = inorderSuccessor(root, node)
    node.val = node_successor_val
    return root
    elif node.val > key:
    pre = node
    node = node.left
    else:
    pre = node
    node = node.right
    return root
    ### BST的相关编程题 来自leetcode.

二叉搜索树中第K小的元素

问题陈述:给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。

说明: 你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。

示例 1:

输入: root = [3,1,4,null,2], k = 1 3 /   1 4     2 输出: 1

示例 2:

输入: root = [5,3,6,2,4,null,null,1], k = 3 5 /   3 6 /   2 4 / 1 输出: 3

进阶: 如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化kthSmallest函数?

python实现:

  • 方法一:利用递归中序遍历得到排好序的序列,然后获取第k个位置上的元素。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Solution:
    def kthSmallest(self, root: TreeNode, k: int) -> int:
    current = 0
    list_node = []
    def inoder(root):
    if root.left != None:
    inoder(root.left)

    list_node.append(root.val)

    if root.right != None:
    inoder(root.right)
    inoder(root)
    return list_node[k-1]
  • 方法二进阶:利用迭代找到第k个元素后就返回
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Solution:
    def kthSmallest(self, root: TreeNode, k: int) -> int:
    def K_inorder(root, K_inorder):
    node = root
    stack = []
    count = 0
    while node or len(stack) > 0:
    if node:
    stack.append(node)
    node = node.left
    else:
    count += 1
    node = stack.pop()
    if count == k:
    return node.val
    node = node.right

    return K_inorder(root, k)
    #### 二叉搜索树中的众数 问题描述:给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。

假定 BST 有如下定义:

结点左子树中所含结点的值小于等于当前结点的值 结点右子树中所含结点的值大于等于当前结点的值 左子树和右子树都是二叉搜索树 例如: 给定 BST [1,null,2,2],

1   2 / 2 返回[2].

提示:如果众数超过1个,不需考虑输出顺序

进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)

python中序遍历实现: 借助字典记录出现次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
from collections import defaultdict
class Solution:
def findMode(self, root: TreeNode) -> List[int]:
if not root:
return []
count = defaultdict(int)
def inorder(root):
if not root:
return
count[root.val] += 1
inorder(root.left)
inorder(root.right)
inorder(root)
max_count = max(count.values())
result = []
for item in count.items():
if item[1] == max_count:
result.append(item[0])
return result
#### 把二叉搜索树转换为累加树

问题描述:给出二叉搜索树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点node的新值等于原树中大于或等于 node.val 的值之和。

提醒一下,二叉搜索树满足下列约束条件:

  • 节点的左子树仅包含键 小于 节点键的节点。
  • 节点的右子树仅包含键 大于 节点键的节点。
  • 左右子树也必须是二叉搜索树。

示例 1

输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8] 输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]

示例 2

输入:root = [0,null,1] 输出:[1,null,1]

示例 3

输入:root = [1,0,2] 输出:[3,3,2]

示例 4

输入:root = [3,2,4,1] 输出:[7,9,4,10]

python迭代实现: 利用迭代按照: 右 -> 中 -> 左 的方式遍历,遍历的同时加上上一个累加值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution:
def convertBST(self, root: TreeNode) -> TreeNode:
def GreatSumTree(root):
count = 0
node = root
stack = []
flag = True
while node or len(stack) > 0:
if node:
stack.append(node)
node = node.right
else:
node = stack.pop()
if flag: ## 最右(最大)节点
count = node.val
flag = False
else:
node.val += count
count = node.val
node = node.left
return root
return GreatSumTree(root)
## 平衡二叉树

平衡二叉树(AVL树--发明者的名字首字母),又称自平衡二叉查找树(self-balancing binary search tree)。平衡二叉树必定是二叉搜索树,反之则不一定。平衡二叉树满足下面的条件: 1. 左结点小于根节点,右结点大于根节点 2. 左子树和右子树的高度差不得超过1。这里通过平衡因子记录左右子树的高度差。

平衡因子:左子树的高度减去右子树的高度。由平衡二叉树的定义可知,平衡因子的取值只可能为0,1,-1,分别对应着左右子树等高,左子树比较高,右子树比较高。

平衡二叉树的查找、插入、删除(时间复杂度都为O(log(n))) -----------------

AVL树失衡旋转的调整过程

B树

B树(Blance-Tree)平衡树,也叫作B-树。一个m阶的B树规定了: 1.根结点至少有两个子女。 2.中间节点包含k-1个元素k个孩子,其中 m/2 <= k <= m 。 3.叶子节点包含k-1个元素,其中 m/2 <= k <= m。 4.每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域划分。 5.所有的叶子结点都位于同一层通俗理解上述定义:B树(B-树)属于平衡的多路搜索树(二叉搜索树是只有两路),与二叉搜索树相比,B树中的节点中可以有多个值,每个值对应的子树满足左子树值均小、右子树值均大。

B树的建立(节点插入)、查找、删除

用处

在数据库索引和文件系统中大量使用B树和B+树。

B+树

B+树在B树(B-树)基础上的优化,查找性能更好。一个m阶B+树具有如下几个特征: 1.中间节点包含有k个元素(B树中是k-1个元素)和K个子树,每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。其中 m/2 <= k <= m。 2.所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。 3.所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。 ### B+树的优点 * 单一节点存储更多的元素,使得查询的IO次数更少。 * 所有查询都要查找到叶子节点,查询性能稳定。 * 所有叶子节点形成有序链表,便于范围查询

参考链接 1.https://blog.csdn.net/sinat_41144773/article/details/89576206?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-3.not_use_machine_learn_pai&depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-3.not_use_machine_learn_pai
2.https://blog.csdn.net/jacke121/article/details/78268602?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3.not_use_machine_learn_pai&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3.not_use_machine_learn_pai

红黑树

红黑树(RB Tree, Red Black Tree)是一种含有红黑结点并能自平衡二叉查找树。它必须满足下面性质: 性质1:每个节点要么是黑色,要么是红色。 性质2:根节点是黑色。 性质3:每个叶子节点(NIL)黑色。 性质4:每个红色结点两个子结点一定都是黑色。 性质5:任意一结点到每个叶子结点路径都包含数量相同的黑结点。 从性质5又可以推出: 性质5.1:如果一个结点存在黑子结点,那么该结点肯定有两个子结点.

参考链接: https://www.jianshu.com/p/e136ec79235c 推荐!