注:资料主要参考算法导论
二叉树常被用作二叉查找树和二叉堆。二叉查找树是一种很特殊的二叉树,弄懂了二叉查找树,再研究二叉树也就很容易了。
二叉排序树(Binary Sort Tree)又称二叉查找树。它或者是一棵空树;或者是具有下列性质的二叉树: (1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值; (3)左、右子树也分别为二叉排序树
无论是树还是图,我们经常需要遍历所有的结点。对于二叉树,一般都使用先序遍历、中序遍历和后序遍历这三种方式。由于二叉查找树的特性,先序跟后序没有太多的意义。
INORDER-TREE-WALK (x)
If x NIL
then INORDER-TREE-WALK (left[x])
print key[x]
INORDER-TREE-WALK (right[x])
如果我们自己不想使用递归调用,可以自己实现一个栈,用循环来实现遍历以提高效率。这种非递归调用的实现在以后的深度优先遍历和广度优先遍历中详细说明。
在树中查找一个给定的关键字(k),可以用以下方式。通过传递树根指针和关键字key,TREE-SEARCH返回指向包含关键字k的结点或返回NULL。
TREE-SEARCH(x, k)
If x = NIL or k = key[x]
then return x
If k < key[x]
then return TREE-SEARCH(left[x], k)
else return TREE-SEARCH(right[x], k)
用循环代替递归,大多数情况下效率要高一些。
同样由于二叉查找树的特性,我们很容易知道,要找出最小的值一顶是顺着根结点,沿着各结点左孩子不断搜索下去,直到最后一个。而最大值则是沿着右孩子一直搜索下去。
另外,二叉查找树中很重要的两个功能是插入和删除结点。这里假设所有结点的关键值都不相等。
插入功能实现简单一些,只需要从根开始不断去对比当前值和需要插入值的大小,来确定插入值的位置应该在哪里。
如果要将新值v插入树中,插入参数是结点Z, key[Z] = V
TREE-INSERT(T, Z)
y <- NIL
x <- root[T]
while x≠NIL
do y <- x
If key[z] <key[x]
then x <- left[x]
else x<-right[x]
p[z] <- y
if y = NIL
then root[T] <- z //空树
else if key[z] < key[y]
then left[y] <- z
else right[y]<- z
删除给定结点z会麻烦一些。有三种情况需要考虑。
需要删除的结点没有左孩子和右孩子,即该结点为叶子结点
需要删除的结点只有左右孩子之一
需要删除的结点有左孩子和右孩子
对于情况1,可以直接删除该结点。对于情况2,同样可以直接删除该结点,然后将它的孩子代替他的位置即可。需要注意的是情况3,因为左右孩子都存在,因为用谁来代替它的位置呢?
我们选用需要删除结点的后继结点y来代替它的位置。顶替z位置的结点关键值必须跟z最接近,那么只有从结点z的前驱结点跟后继结点中选择。前驱结点是它的父节点,无法代替它的位置。所以只能选择后继结点,选用后继结点的另一个原因是因为后继结点y没有左孩子。处理起来也很方便。
# P[z]是结点z的父亲结点。如果未在结点中定义指向父亲结点的指针,可用临时指针保存
TREE-DELETE(T, Z)
If left[z] = NIL or right[z] = NIL
then y <- z
else y <- TREE-SUCCESSOR(z) //找到z的后继结点
If left[y] ≠ NIL //判断结点z是否有孩子,有则用x保存
then x <- left[y]
else x<- right[y]
If x ≠ NIL
then p[x] <- p[y] //如果x不是空,即y有孩子,将x的父亲指针修改
If p[y] = NIL
then root[T] <- x //要删除的结点是根
else if y = left[p[y]] //将父亲指针值修改,作用是删除该结点
then left[p[y]] <- x
else right[p[y]] <- x
If y ≠ z //情况3,用y的key值替换z的key值
then key[z] <- key[y]
Copy y’s satellite data into z
return y
上述过程首先删除一个结点,如果是情况3,删除的是后继结点,需要用后继结点的值去覆盖真正需要删除的结点的值。
头文件 binaryTree.h:
#ifndef __BINARYTREE_H__ #define __BINARYTREE_H__ namespace binarytree { template<typename Element> class CTreeNode { public: Element key; CTreeNode<Element> *lchild; CTreeNode<Element> *rchild; CTreeNode(Element value):key(value),lchild(NULL), rchild(NULL){}; ~CTreeNode(){lchild = NULL; rchild = NULL;}; }; template <typename Element> class CBinaryTree { public: CBinaryTree(); ~CBinaryTree(); bool treeEmpty(); //树是否为空