文章目录
总结
二分搜索树是一种特殊的二叉树,二分树的遍历一共分四种:前序遍历,中序遍历,后序遍历,层序遍历。四种遍历方式的主要区别是:节点的访问顺序不同。
层序遍历与先序、中序、后序遍历不同。层序遍历用到了队列,而先、中、后序需要用到栈。
因此,先、中、后序遍历 可以 采用递归方式来实现,而层序遍历则没有递归方式。
本博客以二分搜索树为例,分别从添加元素,查询元素,四种遍历方式,删除元素四个方面进行讲述。(一般的二叉树也是同理的,尤其是遍历操作,思路和代码是完全一样的)
二分搜索树
定义树的节点类:
class TreeNode:
def __init__(self,item):
self.val = item
self.left = None
self.right = None
一、添加元素
def add(self, root, elem): # 往二分搜索树中添加元素,并返回BST的根节点
if not root:
root = TreeNode(elem)
self.size += 1
return root
if elem < root.val:
root.left = self.add(root.left, elem)
else:
root.right = self.add(root.right, elem)
return root
二、查询元素
def contains(self, root, elem): # 在BST中找寻元素elem
if not root or not elem:
return False
if root.val == elem:
return True
elif elem < root.val:
return self.contains(root.left, elem)
else:
return self.contains(root.right, elem)
三、遍历
可以使用递归的写法,也可以使用非递归的方法。前序遍历的递归和非递归写法都要掌握,中序和后序遍历只需掌握递归写法即可,非递归写法不仅复杂,而且实际应用不广。
1. 前序遍历(也叫深度优先遍历)
深度优先遍历的非递归写法要借助栈来实现。
def preorder(self, root): # 递归
if not root:
return
print(root.val)
self.preorder(root.left)
self.preorder(root.right)
# 前序遍历的非递归写法,借助栈来实现,依次将右节点和左节点加入栈中
def preorder_2(self, root):
if not root:
return
stack = [root]
while stack:
cur = stack.pop()
print(cur.val)
if cur.right:
stack.append(cur.right)
if cur.left:
stack.append(cur.left)
2. 中序遍历
中序遍历二分搜索树,可以得到顺序序列。
def inorder(self, root):
if not root:
return
self.inorder(root.left)
print(root.val)
self.inorder(root.right)
中序遍历的非递归写法:
def inorder(root):
if not root:
return
res, stack = [], []
while root or stack:
while root:
stack.append(root)
root = root.left
## 此时的stack栈顶存放的是最左端的节点
else: # if stack:
root = stack.pop()
res.append(root)
root = root.right
return res
3. 后序遍历
def postorder(self, root):
if not root:
return
self.postorder(root.left)
self.postorder(root.right)
print(root.val)
4. 层序遍历(广度优先遍历)
层序遍历的递归方式:将当前节点,当前所在的level以及结果数组传递给递归函数。在递归函数中,取出节点的值,添加到level参数对应结果数组元素中。
广度优先遍历的非递归写法要借助队列来实现。
def levelorder(self, root): # 层序遍历(广度优先遍历)的非递归实现
if not root:
return
queue = [root]
while queue:
cur = queue.pop(0)
print(cur.val)
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
四、删除元素
1. 删除BST的最小值和最大值
第一步, 我们先要获得BST的最大值或最小值(注意:最大值或最小值不一定是叶节点,例如下图)
def minimum(self, root): # 得到BST的最小值(注意,最小值不一定是叶节点)
assert self.size > 0
if not root.left:
return root.val
return self.minimum(root.left)
def maximum(self, root): # 得到BST的最大值
assert self.size > 0
if not root.right:
return root.val
return self.maximum(root.right)
第二步,删除最小值/最大值,如果最值是叶节点,那么很容易,直接删除即可,如果最值不是叶节点,还需要将其子树整体往上搬一级。
def removeMin(self, root):
if not root.left: # 如果不含左子树,则root为最小节点,需要删除
rightTree = root.right # 先将root的右子树保存下来
root.right = None # 释放root的right子树
self.size -= 1
return rightTree # 返回root的右子树即可(因为root没有左子树)
# 如果包含左子树,则递归查找最小的元素
root.left = self.removeMin(root.left) # 把root和左子树接上
return root
def removeMax(self, root):
if not root.right:
leftTree = root.left
root.left = None
self.size -= 1
return leftTree
root.right = self.removeMax(root.right)
return root
如果想要删除了最小节点之后,返回最小节点的值,可以将minimum()函数和removeMin()函数结合起来用:
def combine_min(self, root):
min_value = self.minimum(root)
self.removeMin(root)
return min_value
2. 删除BST的任意节点
根据待删除节点的位置,可以分为如下四种情况:
- 删除只有左孩子的节点
- 删除只有右孩子的节点
- 删除叶子节点
- 删除左右都有孩子的节点(最复杂,是最一般的情况,包括了前三种)
例如,删除图中的节点14,需要将其左右子树融合起来。找14的后继节点(右子树中最小的节点,即15)来取代14,(同理,也可以使用14的前驱节点来替换14,即用12来替换,效果是一样的,都能保持BST的平衡)
#### 删除BST中任意元素(并用后继结点代替它)
def remove(self, root, elem): # 删除元素elem对应的节点
if not root or not elem:
return
if elem < root.val:
root.left = self.remove(root.left, elem) # 在左子树中找elem,返回的子树要用root.left接住
elif elem > root.val:
root.right = self.remove(root.right, elem)
else: # elem == root.val
if not root.left: # 不含左子树时,直接将右子树返回即可
return root.right
elif not root.right:
return root.left
else: # 既有左子树又有右子树
successor = self.minimum(root.right) # 找到后继结点,先拿出来
successor.right = self.removeMin(root.right) # 把原先的后继结点删除,并且把root的左右子树接到后继结点上
successor.left = root.left
return successor
return root