顺序存储的线性表查找我们可以使用折半、插值、斐波那契等查找算法来实现,因为有序,所以在插入和删除上就需要耗费大量的时间,今天要讨论的算法既可以获得不错的插入删除效率,也可以比较高效的实现查找——二叉排序树。
二叉排序树又叫二叉查找树,它或者是一棵空树,或者是具有下列性质的二叉树。
1、如果它的左子树不为空,则左子树上所有节点的值均小于它的根节点的值。
2、如果它的右子树不为空,则右子树上所有节点的值均大于它的根节点的值。
3、它的左右子树也分别为二叉排序树。
从二叉排序树的定义可知,左子树节点一定比双亲节点小,右子树节点一定比双亲节点大。构造一颗二叉树的目的并不是为了排序,而是为了提高插入和删除关键字的速度。毕竟在一个有序数据集上的查找,速度=总是要快于无序的数据集的。
二叉排序树的查找
typedef struct BiNode
{
int data;
struct BiNode *lChild, *rChild; /*左右孩子指针*/
};
typedef struct BiNode* BiTree;
/*递归查找二叉排序树T中是否存在key*/
/*指针f指向T的双亲,其初始调用值为NULL*/
/*若查找成功,则指针p指向该数据元素节点,并返回TRUE*/
/*指针p指向查找路径上访问的最后一个节点,并返回FALSE*/
Status SearchBST(BiTree T, int key, BiTree f, BiTree* p)
{
if (!T)
{
*p = f;
return FALSE;
}
else if (key == T->data)
{
*p = T;
return TRUE;
}
else if(key < T->data)
{
return SearchBST(T->lChild, key, T, p);//在左子树继续查找
}
else
{
return SearchBST(T->rChild, key, T, p);//在右子树继续查找
}
}
代码很简单,不再赘述。接下来看下二叉排序树的插入操作
二叉排序树的插入
/*当二叉排序树中不存在关键字等于key的数据元素时,插入key并返回TRUE,否则返回FALSE*/
Status InsertBST(BiTree T, int key)
{
BiTree p, s;
if (!SearchBST(T, key, NULL, &p))
{
s = (BiTree)malloc(sizeof(BiNode));
s->data = key;
s->lChild = NULL;
s->rChild = NULL;
if (!p) //首次插入时特殊处理
{
T = s;
}
else if (key < p->data)
{
p->lChild = s;
}
else
{
p->rChild = s;
}
return TRUE;
}
else
{
return FALSE;
}
}
插入的过程就是构造二叉排序树的过程。
二叉排序树的删除
当我们想删除二叉排序树的节点时,按照孩子节点的数量可分为以下三种情况:
1、叶子节点,直接删除即可
2、仅有左子树或右子树的节点,删除该节点,将它的左子树或右子树移动到删除节点的位置即可
3、既有左子树又有右子树,这时我们可以对二叉排序树中序遍历,得到待删除节点的前驱和后继节点,用前驱或者后继节点来代替原来待删节点的位置。
实现算法如下:
Status Delete(BiTree p)
{
BiTree q, s;
if (p->rChild == NULL)
{
q = p;
p = p->lChild;
free(q);
}
else if (p->lChild == NULL)
{
q = p;
p = p->rChild;
free(q);
}
else
{
q = p;
s = p->lChild;
while (s->rChild) //寻找待删节点左子树的最末右子树,即待删节点的前驱(也可以找后继节点)
{
q = s;
s = s->rChild;
}
p->data = s->data;
if(p != q)
{
q->rChild = s->lChild; //重接q的右子树
}
else
{
q->lChild = p->lChild; // 重接q的左子树
}
free(s);
}
return TRUE;
}
Status DeleteBST(BiTree T, int key)
{
if (!T) //不存在关键字等于key的数据元素
{
return FALSE;
}
else
{
if (key == T->data)
{
return Delete(T);
}
else if (key < T->data)
{
return DeleteBST(T->lChild, key);
}
else
{
return DeleteBST(T->rChild, key);
}
}
}
上述代码中我们是使用带删除节点的前驱来替换待删节点,使用后继节点也是一样的,这里不再赘述。
总结:
二叉排序树保持了链式存储结构在执行插入和删除操作时不需要移动元素的优点,只要找到合适的插入和删除位置后,仅需修改链接指针即可。对于二叉排序树的查找,走的就是从根节点到要查找节点的路径,比较次数等于给定的节点在二叉排序树中的层数,最少为1次,最多也不会超过树的深度。因此其时间复杂度为O(logn)。