1.前言
之前有讲过一种种特殊的树,就是二叉排序树,下面我将详细说明他们的操作。
2.二叉排序树
左子树上所有结点的关键字均小于根结点的关键字;右子树上的所有结点的关键字均大于根结点的关键字;左子树和右子树又各是一棵二叉排序树。
推广一下,只要存在顺序性,例如左子树的关键字都大于根结点的关键字,且右子树的关键字小于根结点,这样也可以称为二叉排序树。以下我们以左<根<右为例。
1.性质
1、如果他的左子树不空,则左⼦树上所有结点的值均⼩于它的根结点的值。
2、若它的右子树不空,则右⼦树上所有结点的值均⼤于它的根结点的值。
3、它的左、右树⼜分为⼆叉排序树。
很显然,⼆叉排序树的定义是⼀个递归形式的定义,所以对于⼆叉排序树的操作都是基于递归的⼆叉排序树既然名字中带有排序⼆字,这就是它相对于普通⼆叉树的优势所在了。
2.结点结构
typedef struct BSTnode
{
int data;
struct BSTnode *l;
struct BSTnode *r;
}BSTNode,*BSTree;
3.插入操作
//非递归
BSTree insert(BSTree ro, int x)
{
BSTNode * p = ro;
BSTNode *pre = NULL;
while (p != NULL)
{
if (x < p->data)
{
pre = p;
p = p->l;
}
else
{
pre = p;
p = p->r;
}
}
BSTNode * s = new BSTNode;
s->data = x;
s->l = s->r = NULL;
if (s->data < pre->data)
{
pre->l = s;
}
else
{
pre->r = s;
}
return ro;
}
//递归版本
BSTree insert_plus(BSTree ro, int x)
{
if (ro== NULL)
{
BSTNode *s = new BSTNode;
s->data = x;
s->l = s->r = NULL;
return s;
}
if (x< ro->data)
{
ro->l=insert_plus(ro->l, x);
return ro;
}
else
{
ro->r = insert_plus(ro->r, x);
return ro;
}
}
4.查找操作
void search(BSTree ro, int x)
{
BSTNode * p = ro;
while (p != NULL && p->data != x)
{
if (x < p->data)
{
p = p->l;
}
else if(x>p->data)
{
p = p->r;
}
if (p == NULL)
{
cout << "不存在" << endl;
return;
}
else
{
cout << "存在" << endl;
return;
}
}
}
5.删除操作
删除操作要分为三种情况
1.被删除结点是叶子结点时
直接从⼆叉排序树当中移除即可,也不会影响树的结构。
删除4:
2.被删除的结点仅有⼀个孩⼦
将孩子的结点(子树)顶替掉要删除的结点即可
如图删除14:
如图删除10:
在这里,我们会发现,如果删除的结点是叶子结点,我们假设他有他有两个左右孩子NULL,我们先行判断一个边没有孩子(即为NULL),另一个边我们不做判断,直接当做有一个孩子来处理,来顶替掉我们要删除的结点,那他的操作就会和仅有一个孩子的删除操作是相同的,我们便可以简化代码。
3.被删除结点的左右孩子都存在
我们要找到被删除结点的后继(前驱),先将被删除结点的数据替换成他的后继(前驱)结点的数据,然后在内存中删除后继(前驱)结点。
其实不难看出最后的删除结点的操作是和1,2相同的。
代码展示:
//找前驱
BSTNode *findp(BSTree ko)
{
while (ko->r != NULL)
{
ko = ko->r;
}
return ko;
}
//找后继
BSTNode * findr(BSTree ko)
{
while (ko->l != NULL)
{
ko = ko->l;
}
return ko;
}
//删除操作
/*
1.x所在结点度为2,找到x的中序遍历前驱或后继结点q,用q的值顶替x的值,问题转化为去x的子树中删除q
x中序遍历前驱结点:x左子树的最右边
x中序遍历后继结点:x右子树的最左边
2.x所在结点度为1,x只有一个孩子,直接将x结点free掉,让其唯一的孩子顶替x的位置
3.x所在结点度为0,(可以认为他的孩子为空孩子)将他也按照度为1的结点操作
*/
BSTree BSTdelete(BSTree ro, int x)
{
if (ro == NULL)
{
cout << "树空" << endl;
return NULL;
}
if (x < ro->data)
{
ro->l = BSTdelete(ro->l, x);
}
else if (x > ro->data)
{
ro->r = BSTdelete(ro->r, x);
}
else
{
//x==ro->data,ro就是当前要找的结点
if (ro->r != NULL && ro->l != NULL)
{
//度为2
//找到中序遍历前驱
/*BSTNode * p = findp(ro->l);
ro->data = p->data;
ro->l = BSTdelete(ro->l, p->data);*/
//找到中序遍历后继
BSTNode * p = findr(ro->r);
ro->data = p->data;
ro->r = BSTdelete(ro->r, p->data);
}
else
{//本质上最后删除的都是叶子(一个结点)到这层判断就是该函数的最后一步,开始return递归
//度为1或者为0
BSTNode * p = ro;
if (ro->l == NULL)
{
ro = ro->r;
}
else
{
ro = ro->l;
}
delete p;
p = NULL;
return ro;//到这里递归结束,开始逐步给ro的上层递归赋值
}
}
return ro;//最外层返回值
}
6.时间复杂度分析
⼆叉排序树的插⼊和查找、删除操作的最坏时间复杂度为 O(h),其中 h 是⼆叉排序树的⾼ 度。最极端的情况下,我们可能必须从根结点访问到最深的叶⼦结点,斜树的⾼度可能变成n,插⼊和删除操作的时间复杂度将可能变为O(n) 。这也是⼆叉排序树在 进⾏多次插⼊操作后可能发⽣的不平衡问题,也是⼆叉排序树的缺陷所在,但这依旧不妨碍其作为⼀个伟大的数据结构。
为了优化二叉排序树的缺陷,我们便提出了二叉平衡树,我会在下一节做出解释。