准备在期末之前解决掉红黑树。为了解决红黑树,首先学习二叉查找树,然后学习平衡的二叉查找树(这里的关键是树的旋转操作)。
最后在已经有上面知识的基础上,再来解决红黑树。至于B树家族,实在是太复杂,自己先了解下。等到自己实力增强的时候再去深入。
首先我们来看看二叉查找树:
下面是二叉查找树的定义:
最后在已经有上面知识的基础上,再来解决红黑树。至于B树家族,实在是太复杂,自己先了解下。等到自己实力增强的时候再去深入。
首先我们来看看二叉查找树:
下面是二叉查找树的定义:
二叉排序树或者是一棵空树;或者
是具有如下特性的二叉树:
(1)若它的左子树不空,则左子树上所有结点的值均小于根结点的值
(2)若它的右子树不空,则右子树上
所有结点的值均大于根结点的值
(3)它的左、右子树也都分别是二叉
排序树
给出一颗二叉查找树:
![](https://img-my.csdn.net/uploads/201212/15/1355562089_6680.jpg)
我们可以看到,根节点为8。左子树的所有节点的数据都比8小,右子树的所有节点的数据都比8大。
而且子树也满足下面的规律。
二叉查找树的概念我们了解起来应该不难。
我们用二叉链表的数据结构就可以来表示二叉查找树:
typedef struct BiTNode{
TElemType data;
struct BiTNode *lchild, *rchild, *parent;
}BiTNode, *BiTree;
下面是具体的基本算法实现。
1.查找,这里使用了递归来实现
Status SearchBST(BiTree T, KeyType key, BiTree f, BiTree &p)
//查找成功, p指向该数据元素的结点
//查找不成功,p指向查找路径上访问的最后一个节点,f指向当前访问的结点的双亲,初始调用为NULL
{
if (T == NULL)
{
p = f;
return false;
}
else
{
if (T->data == key)
{
p = T;
return true;
}
else if (key < T->data)
{
f = T;
SearchBST(T->lchild, key, f, p);
}
else
{
f = T;
SearchBST(T->rchild, key, f, p);
}
}
}
2.插入
插入只有在查找失败的情况下面才进行。因为根据定义,二叉查找树不允许节点有相同的数据。
下面是插入算法
//二叉排序树的插入算法,只有在查找不成功的时候才进行插入
Status InsertBST(BiTree &T, TElemType e)
{
BiTree p;
BiTree s;
if (!SearchBST(T, e, NULL, p))
//不存在就插入
{
s = (BiTree) malloc (sizeof(BiTNode));
s->data = e;
s->lchild = s->rchild = NULL;
if (!p)
T = s;
else if (e < p->data)
p->lchild = s;
else
p->rchild = s;
return true;
}
else
return false;
}
3.中序遍历的前驱和后继
根据算法导论来好了。
为什么这里要强调中序遍历的前驱和后继?因为在下面的删除操作中我们会用到求前驱或者是求后继的操作。
那么我们在中序遍历的前驱和后继之前,我们先写两个查找最大节点和最小节点的算法。因为我们在 进行中序遍历的前驱和后继的时候要用到。
//最大关键字元素
BiTree BSTMaxmum(BiTree b)
{
BiTree p = b;
while (p->rchild)
p = p->rchild;
return p;
}
//最小关键字元素
BiTree BSTMinmum(BiTree b)
{
BiTree p = b;
while (p->lchild)
p = p->lchild;
return p;
}
有了这两个作为基础。现在我们来介绍下中序遍历的前驱和后继
我们以求后继作为例子:
求后继分为两种情况
1.如果结点的右子树非空,那么我们右子树的最左结点就是该节点的后继
2.如果结点的右子树为空,那么从x向上查找,直到遇到某个是其父结点的左儿子的结点为止。
下面以后继为例子,具体代码的实现。
//查找后继
BiTree SuccessorBST(BiTree b)
{
BiTree p = b;
//如果结点的右子树非空,那么右子树的最左结点就是该节点的后继
if (p->rchild)
return BSTMinmum(p->rchild);
//如果结点的右子树为空,那么从x向上查找,直到遇到某个是其父结点的左儿子的结点为止
BiTree y = p->parent;
while (y != NULL & p == y->rchild)
{
p = y;
y = y->parent;
}
return y;
}
前驱的方法与查找后继类似。具体代码如下:
//查找前驱
BiTree PredecessorBST(BiTree b)
{
BiTree p = b;
if (p->lchild)
return BSTMaxmum(p->lchild);
BiTree y = p->parent;
while (y != NULL & p == y->lchild)
{
p = y;
y = y->parent;
}
return y;
}
4.结点的删除
结点的删除时我们要保证删除后的结点仍然符合 二叉查找树的性质。
大家可以参考下面这个博客:
http://blog.csdn.net/danielhf/article/details/81210
http://www.cnblogs.com/aiyelinglong/archive/2012/03/27/2419972.html
删除分为三种
1. 要删除的节点没有子节点, 即它是叶节点
![](https://img-my.csdn.net/uploads/201212/15/1355578862_9812.png)
2. 要删除的节点有一个子节点
![](https://img-my.csdn.net/uploads/201212/15/1355578825_5119.png)
3. 要删除的节点有两个子节点
![](https://img-my.csdn.net/uploads/201212/15/1355578874_7108.png)
这里有个比较巧妙地方法,我们把第1种情况和第2种情况合并起来处理
如果左子树为空,就重接右子树;如果右子树为空,就重接左子树
最难的就是第三种情况了。
通过看图,我们来理解一下。
我们要删除 z 这个节点
1.我们通过中序遍历找到z的后继为y,我们已经知道y肯定没有左子树(如果这里不明白,再回上去看看吧)
2.把y结点的值赋给z
3.我们要删除y结点 ,让y的父亲结点成为y的右子树的结点 (自己唯一不明白的就是这个地方了)
最后来看看删除代码:
Status DeleteBST(BiTree &T, KeyType key)
{
//寻找删除的结点的位置
if (!T)
return false;
if (T->data == key)
{
Delete(T);
return true;
}
else if (key < T->data)
DeleteBST(T->lchild, key);
else
DeleteBST(T->rchild, key);
}
使用查找前驱的方法删除结点:
void Delete (BiTree &p)
{
BiTree q, s;
q = p;
//左子树为空,则重接右子树
if(!p->lchild)
{
p = p->rchild;
free(q);
}
//右子树为空,则重接左子树
else if(!p->rchild)
{
p = p->lchild;
free(q);
}
//左右子树都不为空
//找到前驱 (后继)
else
{
//s = PredecessorBST(p->lchild);
s = p->lchild;
while(!s->rchild)
{
q = s;
s = s->rchild;
}
//s为p的前驱
p->data = s->data;
if (q != p)
q->rchild = s->lchild;
else
q->lchild = s->lchild;
free(s);
}
}