概念
二叉排序树很重要的性质就是左子树的内容 小于 根节点 小于 右子树,并且具有递归的性质,也就是每一个子树都满足。
按照这个顺序,我们难免想到中序遍历,因为中序遍历就是左中右的顺序,使得我们的遍历结果为已排序序列。
这里不提中序遍历,如果有问题看这里。(C代码,稍微改一下就行)
我们提一下寻找一个结点p的上一个结点和下一个结点的方式(如果没有返回None)。
上一个结点
按照左中右的顺序,如果有左子树,那上一个结点一定在左子树中。
那么应该是哪一个呢? 是左子树遍历的最后一个结点,也就是左子树的最右结点。
如果没有,事情就麻烦了。这时候,和问题相关的就只剩下父亲结点了。
如果没有父亲结点,也就是当前结点为根节点,直接返回none;
否则我们看一下父亲结点和当前结点的关系:
- 如果当前结点是父亲结点的右孩子,那么父亲结点就是前一个;
- 如果当前结点是父亲结点的左孩子,那么事情就麻烦了。(注意这里不是没有)
看一下这张图:
只有这种情况,不过可能没这么好运,上两代就找到了,也可能是n代,也可能找不到。
下一个结点
这个基本上同理,还是有右孩子直接起飞,找到右子树的最左边结点即可。
没有,我们就需要找到一个将这部分当成左子树的祖先结点,和上面的长得差不多。
实现一下
class text(LinkedBinaryTree):
# 下一个结点
def after(self,p):
if self.right(p) is not None:
# 有右子树
walk = self.right(p)
while self.left(walk):
walk = self.left(walk)
return walk
else:
walk = p
# 退出条件:祖先结点为空或者是祖先结点的左孩子是walk
ancestor = self.parent(walk)
while ancestor is not None and walk == self.right(ancestor):
walk, ancestor = ancestor, self.parent(ancestor)
return ancestor
这里提到的LinkedBinaryTree就是一个二叉树,只要支持parent、left和right即可(对于没有的,要置为none)。
相似的,我们就有before函数
def before(self,p):
if self.left(p) is not None:
# 有左子树
walk = self.left(p)
while self.right(walk):
walk = self.right(walk)
return walk
else:
walk = p
ancestor = self.parent(walk)
while ancestor is not None and walk == self.left(ancestor):
walk, ancestor = ancestor, self.parent(ancestor)
return ancestor
操作
搜索
因为二叉排序树的性质,我们在查找一个元素的时候,只需要和根节点比较即可:
- 等于根节点的,不用说就是找到了
- 小于根节点的,很明显应该是在左子树上的
- 大于根节点,那就是在右子树上
当我们发现要找的左子树/右子树已经为空了,说明是没找到,但是这个地方可不是随便给的。
当我们要进行插入操作,插入的位置就是我们找到的空位置。
代码:
def TreeSearch(self,T, p, k):
if k==p.key():
return p
elif k<p.key() and T.left(p) is not None:
return self.TreeSearch(T,T.left(p),k)
elif k>p.key() and T.right(p) is not None:
return self.TreeSearch(T,T.right(p),k)
else:
return p
插入删除
插入删除的操作,还是要基于查找案来完成的。
插入的话,我们先明白一个事——插入的位置一定是叶子节点。
因为我们已经有了p结点,只需要判断一下是左孩子还是右孩子即可。
删除比较烦,如果是没孩子,直接删;
有一个也不怕,把孩子往上提一下即可;
如果是有两个孩子,就有一点难了,因为要考虑性质,我们有这样两个选择:
- 将要删除的前一个元素拿出来作为根节点
- 将要删除的后一个元素拿出来作为根节点
因为拿出来的结点最多有一个子树(这个看上面,都走到最后了),所以只需要提一下即可。