Dongxing's Wiki Dongxing's Wiki
首页
  • 剑指 Offer
  • LeetCode
  • 算法与数据结构
  • Python 语言
  • Web 开发
  • Hive
  • Elastic Search
  • 机器学习
  • NLP
  • 检索技术
  • 数据分析
  • 经验笔记
  • Linux 配置
  • 博客进化记
  • 杂谈
GitHub (opens new window)
首页
  • 剑指 Offer
  • LeetCode
  • 算法与数据结构
  • Python 语言
  • Web 开发
  • Hive
  • Elastic Search
  • 机器学习
  • NLP
  • 检索技术
  • 数据分析
  • 经验笔记
  • Linux 配置
  • 博客进化记
  • 杂谈
GitHub (opens new window)
  • 算法与数据结构

    • 基础排序
    • 算法与数据结构 | 高级排序
    • 算法与数据结构 | 堆和堆排序
    • 算法与数据结构 | 二叉搜索树
      • 二分查找法
      • 二叉搜索树
        • 二叉搜索树的应用
        • 支持重复元素的二叉搜索树
      • 树的变种
      • 树形问题
        • 递归方法,天然就有一种树的性质
  • 算法与数据结构
  • 算法与数据结构
anthony
2018-11-20
目录

算法与数据结构 | 二叉搜索树

# 二叉搜索树

二叉查找树(英语:Binary Search Tree),也称为二叉搜索树、有序二叉树(ordered binary tree)或排序二叉树(sorted binary tree),是-指一棵空树或者具有下列性质的二叉树:

  • 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  • 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  • 任意节点的左、右子树也分别为二叉查找树;
  • 没有键值相等的节点。

二叉搜索树在查找当中用途非常广泛。

# 二分查找法

对于有序数组,才能使用二分查找法。时间复杂度 O(logn)

def binary_search(arr, target):
    # return the index of target or -1 if target is not found
    # 在 [l...r] 之间查找target
    l = 0
    r = len(arr) - 1
    while l <= r:
        # mid = (l + r) // 2  # 潜在bug:整数溢出
        mid = l + (r - l) // 2
        if arr[mid] == target:
            return mid
        if target < arr[mid]:
            r = mid - 1
        else:
            l = mid + 1
    return -1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
arr = [1, 3, 4, 5, 6, 7, 8, 10, 12]
binary_search(arr, 2)
1
2
-1

# 二叉搜索树

堆一定是完全二叉树,而二叉搜索树未必是完全二叉树,所以堆可以用数组结构来存储,而二叉搜索树就要用树来存储。

下面代码定义了二叉搜索树的结构,并实现了 插入、查找、深度优先遍历、广度优先遍历、删除节点(删除最大值、最小值、任意节点) 等操作。

class Node(object):
    def __init__(self, key, value):
        self.key = key
        self.value = value
        self.left = None
        self.right = None

class BST(object):
    def __init__(self):
        self.root = None
        self.count = 0
    
    def size(self):
        return self.count
    
    def is_empty(self):
        return self.count == 0
    
    def insert(self, key, value):
        self.root = self._insert(self.root, key, value)
    
    def _insert(self, node, key, value):
        # 递归调用,向以node为根的树中,插入键和值分别为key和value的节点。返回插入新节点后这棵树的根。
        if node == None:
            self.count += 1
            return Node(key, value)
        if key == node.key:
            # 一般二叉搜索树中不应有重复的key值。如果插入的key值已存在,则以新的value覆盖旧的value
            node.value = value
        elif key < node.key:
            node.left = self._insert(node.left, key, value)
        else:  # key > node.key
            node.right = self._insert(node.right, key, value)
        return node
            
    def search(self, key):
        return self._search(self.root, key)
    
    def contain(self, key):
        return self._search(self.root, key) != None
    
    def _search(self, root, key):
        # 递归调用,按key查找节点,返回节点的value
        if not root:
            return None
        if root.key == key:
            return root.value
        elif key < root.key:
            return self._search(root.left, key)
        else:
            return self._search(root.right, key)
    
    def pre_order_visit(self):
        self._per_order_visit(self.root)
        
    def _pre_order_visit(self, node):
        # 递归调用,前序遍历
        if node:
            print(node.key)
            self._pre_order_visit(node.left)
            self._pre_order_visit(node.right)
    
    def mid_order_visit(self):
        self._mid_order_visit(self.root)
        
    def _mid_order_visit(self, node):
        # 递归调用,中序遍历
        if node:
            self._mid_order_visit(node.left)
            print(node.key)
            self._mid_order_visit(node.right)
            
    def post_order_visit(self):
        self._post_order_visit(self.root)
        
    def _post_order_visit(self, node):
        # 递归调用,后序遍历
        if node:
            self._post_order_visit(node.left)
            self._post_order_visit(node.right)
            print(node.key)
            
    def level_order_visit(self):
        # 借助队列实现层序遍历
        q = []
        q.append(self.root)
        while len(q) != 0:
            node = q[0]
            q.pop(0)
            print(node.key)
            if node.left:
                q.append(node.left)
            if node.right:
                q.append(node.right)
    
    def minimum(self):
        if not root:
            return None
        min_node = self._minimum(self.root)
        return min_node.key
    
    def _minimum(self, node):
        # 递归查找最小值。其实就是一直向左下方寻找,最左下的就是最小值
        if not node.left:
            return node
        return self._minimum(node.left)
    
    def maximum(self):
        if not root:
            return None
        max_node = self._maximum(self.root)
        return max_node.key
    
    def _maximum(self, node):
        # 递归查找最大值。其实就是一直向右下方寻找,最右下的就是最大值
        if not node.right:
            return node
        return self._maximum(node.right)
    
    def remove_min(self):
        # 删除最小值。最小值节点肯定没有左孩子,如果也没有右孩子,那直接删掉即可。如果有右孩子,则将其作为最小值节点的父节点的左孩子。
        if self.root:
            self.root = self._remove_min(self.root)
    
    def _remove_min(self, node):
        # 删除以node为根的子树中的最小节点,并返回新的这棵子树的根
        if not node.left:  # 是最小节点了
            right = node.right  # 一句话即涵盖了两种情况:有右孩子则将右孩子作为新根,没有右孩子则将None作为新根
            del node
            self.count -= 1
            return right
        # 不是最小节点,继续往左下方寻找
        node.left = self._remove_min(node.left)
        return node
    
    def remove_max(self):
        # 删除最大值。最大值节点肯定没有右孩子。如果也没有左孩子,直接删掉;如果有左孩子则将左孩子作为该最大值节点的父节点的右孩子即可。
        if self.root:
            self.root = self._remove_max(self.root)
        
    def _remove_max(self, node):
        # 删除以node为根的子树中的最小节点,并返回新的这棵子树的根
        if not node.right:  # 是最大节点了
            left = node.left
            del node
            self.count -= 1
            return left
        # 不是最大节点,继续往右下方寻找
        node.right = self._remove_max(node.right)
        return node
    
    def remove(self, key):
        # 删除任意节点
        # 如果节点只有左子树或者右子树,可以按照上面 删除最大值和最小值的方法,直接顶位即可。
        # 如果左右都有,则比较复杂,按照二叉搜索树的性质,当前节点值大于左子树,且小于右子树,所以应当用右子树中的最小值节点来顶位。
        # 具体操作:
        #(1)要删的节点为d
        #(2)找到d的右子树的最小节点s,用于顶替d,另存为节点n
        #(3)按照之前remove_min的方法,把d的右子树中的最小节点s删除掉
        #(4)remove_min方法会返回删除了最小节点s之后的右子树的新的根,作为n的右子树
        #(5)n的左子树,就是原d的左子树
        #(6)删除d,n是新的根,返回回去,完成删除
        # 可以画图辅助理解
        self.root = self._remove(self.root, key)
        
    def _remove(self, node, key):
        # 递归,从当前node为根的子树中删除key,并返回删除之后的新的根
        
        if not node:
            return None
        if key < node.key:
            node.left = self._remove(node.left, key)
            return node
        elif key > node.key:
            node.right = self._remove(node.right, key)
            return node
        else:  # key == node.key
            if not node.left: # 只有右孩子,或者左右都为空,直接删
                right = node.right
                del node
                self.count -= 1
                return right
            if not node.right: # 只有左孩子,直接删
                left = node.left
                del node
                self.count -= 1
                return left
            # 左右两孩子都有
            s = self._minimum(node.right) # 找到右子树的最小值,作为替补节点
            n = Node(s.key, s.value) # s要复制一份,不然待会儿s指向的节点,就被删掉了。n作为新的根
            n.right = self._remove_min(node.right) # 删掉原s节点,并处理新根的右子树
            n.left = node.left  # 处理新根的左子树
            del node
            return n
            

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
tree = BST()
tree.insert(6, '666')
tree.insert(2, '222')
tree.insert(3, '333')
tree.insert(4, '444')
tree.insert(1, '111')
print(tree.search(5))
print(tree.search(2))
1
2
3
4
5
6
7
8
None
222
tree.mid_order_visit()
1
1
2
3
4
6
tree.remove_min()
tree.mid_order_visit()
1
2
2
3
4
6
tree.remove_max()
tree.mid_order_visit()
1
2
2
3
4
tree.remove(2)
tree.mid_order_visit()
1
2
3
4

# 二叉搜索树的应用

找最大,找最小,找排序的前驱、后继,找floor、ceil。

找rank。例如,key=58是从小到大排名第几的元素?不过这个需要额外在二分搜索树中每个节点增加一个信息,存放以该节点为根的子树中总的节点数目。这样以58为例,11-5+3=9,说明58前面一共有9个节点,58排名第10.

# 支持重复元素的二叉搜索树

1)修改树定义,定义左孩子为小于等于,右孩子为大于 2)修改节点定义,一个节点可存放多个重复的值

# 树的变种

二叉搜索树的局限性

由于插入顺序的不同,同样的数据,可能对应着不同的二叉搜索树的结构。例如,极端情况下,1,2,3,4,5,6,依次插入,则会形成一棵严重偏斜的树,所有节点都放在右子树,二叉搜索树退化为链表了。

解决方案 —— 平衡二叉树:(知名实现:红黑树,还有 2-3树,AVL树,splay树 等实现)。(左右两子树的高度差不超过1)

还有其他树:Treap(二叉搜索树和堆结合),trie(字典树,每个节点是一个字母,一条路径代表一个单词)

# 树形问题

# 递归方法,天然就有一种树的性质

例如归并排序和快速排序,不断从大到小分拆排序,就类似于树的形状。

搜索问题:例如决策树,通过树状遍历所有决策。

上次更新: 2022/11/11, 2:11:00
算法与数据结构 | 堆和堆排序

← 算法与数据结构 | 堆和堆排序

Theme by Vdoing | Copyright © 2017-2023 anthony 京ICP备17072417-3
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式