二叉排序树
1.二叉排序树定义
二叉排序树(Binary Sort Tree)或者是一棵空树;或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于根结点的值;若右子树不空,则右子树上所有结点的值均大于根结点的值。
(2)左右子树也都是二叉排序树,如图6-2所示。
2.二叉排序树的查找过程
由其定义可见,二叉排序树的查找过程为:
(1)若查找树为空,查找失败。
(2)查找树非空,将给定值key 与查找树的根结点关键码比较。
(3)若相等,查找成功,结束查找过程,否则:
① 当给值key 小于根结点关键码,查找将在以左孩子为根的子树上继续进行,转(1)。 ② 当给值key 大于根结点关键码,查找将在以右孩子为根的子树上继续进行,转(1)。
3.二叉排序树插入操作和构造一棵二叉排序树
向二叉排序树中插入一个结点的过程:设待插入结点的关键码为key ,为将其插入,先要在二叉排序树中进行查找,若查找成功,按二叉排序树定义,该插入结点已存在,不用插入;查找不成功时,则插入之。因此,新插入结点一定是作为叶子结点添加上去的。构造一棵二叉排序树则是逐个插入结点的过程。对于关键码序列为:{63,90,70,55,67,42,98,83,10,45,58},则构造一棵二叉排序树的过程如图6-3所示。
4.二叉排序树删除操作
从二叉排序树中删除一个结点之后,要求其仍能保持二叉排序树的特性。
设待删结点为*p(p 为指向待删结点的指针),其双亲结点为*f,删除可以分三种情况,如图6-4所示。
(1)*p结点为叶结点,由于删去叶结点后不影响整棵树的特性,所以,只需将被删结点的双亲结点相应指针域改为空指针,如图6-4(a )所示。
(2)*p结点只有右子树或只有左子树,此时,只需将或替换*f结点的*p子树即可,如图6-4(b )、(c )所示。
(3)*p结点既有左子树又有右子树,可按中序遍历保持有序地进行调整,如图6-4(d )、(e )所示。
设删除*p结点前,中序遍历序列为:
① P为F 的左子女时有:…,Pi 子树,P ,Pj ,S 子树,Pk ,Sk 子树,…,P2,S2子树,P1,S1子树,F ,…。
② P 为F 的右子女时有:…,F ,Pi 子树,P ,Pj ,S 子树,Pk ,Sk 子树,…,P2,S2子树,P1,S1子树,…。
则删除*p结点后,中序遍历序列应为:
① P 为F 的左子女时有:…,Pi 子树,Pj ,S 子树,Pk ,Sk 子树,…,P2,S2子树,P1,S1子树,F ,…。
② P为F 的右子女时有:…,F ,Pi 子树,Pj ,S 子树,Pk ,Sk 子树,…,P2,S2子树,
P1,S1子树,…。
有两种调整方法:
① 直接令Pi 为*f相应的子树,以Pr 为Pi 中序遍历的最后一个结点Pk 的右子树。 ② 令*p结点的直接前驱Pr 或直接后继(对Pi 子树中序遍历的最后一个结点Pk )替换*p结点,再按(2)的方法删去Pr 或Pk 。
【算法分析】
对给定序列建立二叉排序树,若左右子树均匀分布,则其查找过程类似于有序表的折半查找。但若给定序列原本有序,则建立的二叉排序树就蜕化为单链表,其查找效率和顺序查找一样。因此,对均匀的二叉排序树进行插入或删除结点后,应进行调整,使其依然保持均匀。
3.2.5 二叉排序树
在这一部分我们要掌握的是二叉排序树的概念、查找、插入和删除操作。在以下的知识点中,二叉排序树的删除相对于其他知识点要复杂一些,但只要掌握了规则,题目还是很容易解决的。下面我们先分别给出各部分的介绍及算法实现,再对一些典型题目进行解析。
(1)二叉排序树
二叉排序树是一种常用的动态查找表,下面首先给出它的非递归形式。
二叉排序树是一棵二叉树,它或者为空,或者具有如下性质:
①任一非终端结点若有左孩子,则该结点的关键字值大于其左孩子结点的关键字值。 ②任一非终端结点若有右孩子,则该结点的关键字值小于其右孩子结点的关键字值。 二叉排序树也可以用递归的形式定义,即二叉排序树是一棵树,它或者为空,或者具有如下性质:
①若它的左子树非空,则其左子树所有结点的关键字值均小于其根结点的关键字值。 ②若它的右子树非空,则其右子树所有结点的关键字值均大于其根结点的关键字值。 ③它的左右子树都是二叉排序树。
例如,由关键字值序列(62,15,68,46,65,12,57,79,35)构成的一棵二叉排序树如图3-38所示。
如果对上述二叉排序树进行中序遍历可以得到一个关键字有序序列(12,15,35,46,57,62,65,68,79),这是二叉排序树的一个重要特征,也正是由此将其称为"
二叉排序树" 。
(2)二叉排序树的查找
二叉排序树的结构定义中可看到:一棵非空二叉排序树中根结点的关键字值大于其左子树上所有结点的关键字值,而小于其右子树上所有结点的关键字值。因此在二叉排序树中查找一个关键字值为k 的结点的基本思想是:用给定值k 与根结点关键字值比较,如果k 小于根结点的值,则要找的结点只可能在左子树中,所以继续在左子树中查找,否则将继续在右子树中查找,依此方法,查找下去,至直查找成功或查找失败为止。二叉排序树查找的过程描述如下:
①若二叉树为空树,则查找失败;
②将给定值k 与根结点的关键字值比较,若相等,则查找成功;
③若根结点的关键字值小于给定值k ,则在左子树中继续搜索;
④否则,在右子树中继续查找。
假定二叉排序树的链式存储结构的类型定义如下:
1
2
3
4
5
6 typedef struct linklist{ keytype key; anytype otherelem; struct linklist*lchild; struct linklist*rchild; }Bin_Sort_Tree_Linklist,*Bin_Sort_Tree;
二叉排序树查找过程的描述是一个递归过程,若用链式存储结构存储,其查找操作的递归算法如下所示:
7
8
9 Bin_Sort_Tree_Linklist*bt_search(Bin_Sort_Tree bt,keytype k) {∥在根指针为bt 的二叉排序树上查找一个关键字值为k 的结点, ∥若查找成功返回指向该结点的指针,否则返回空指针
10 if (bt=NULL)‖(bt->key==k)return bt;
11 else if (kkey)return bt_search(bt->lchild,k);
12 ∥在左子树中搜索
13 else return bt_search(bt->rchild,k);//在右子树中搜索
14 }
这个过程也可以用非递归算法实现,算法描述如下:
15 Bin_Sort_Tree_Linklist*bt_search1(Bin_Sort_Tree bt,keytype k)
16 {
17 p=bt;∥指针p 指向根结点,搜索从根结点开始
18 while (p!=NULL && p->key!=k)
19 {
20 if (k
key)p=p->lchild;
21 else p=p->rchild;
22 }
23 return (p);
24 } 3)二叉排序树的插入
在一棵二叉排序树中插入一个结点可以用一个递归的过程实现。若二叉排序树为空,则新结点作为二叉排序树的根结点。否则,若给定结点的关键字值小于根结点关键字值,则插入在左子树上;若给定结点的关键字值大于根结点的值,则插入在右子树上。
下面是二叉排序树插入操作的递归算法。
1
2
3
4
5
6 void bt_insert1(Bin_Sort_Tree*bt,Bin_Sort_Tree_Linklist*pn) {∥在以bt 为根的二叉排序树上插入一个由指针pn 指向的新的结点 if (*bt=NULL)*bt=pn; else if (*bt->key>pn->key)bt_insert 1(&(*bt->lchild),pn); else if (*bt->keykey)bt_insert1(&(*bt->rchild),pn); }
这个算法也可以用非递归的形式实现,如下所示:
7
8
9 void bt_insert 2(Bin_Sort_Tree*bt, Bin_Sort_Tree_Linklist*pn) {
10 p=bt;
11 while (p!=NULL && p->key!=pn->key)
12 {
13 q=p;
14 if (p->key>pn->key)p=p->lchild;
15 else p=p->rchild;
16 }
17 if (p=NULL){
18 if (q->key>pn->key)q->lchild=pn;
19 else q->rchild=pn->key;
20 }
21 } 利用二叉排序树的插入算法,可以很容易地实现创建二叉排序树的操作,其基本思想为:由一棵空二叉树开始,经过一系列的插入操作生成一棵二叉排序树。
例如,由结点关键字序列(62,15,68,46,65,12,57,79,35)构造二叉排序树的过程为:从空二叉树开始,依次将每个结点插入到二叉排序树中,在插入每个结点时都是从根结点开始搜索插入位置,找到插入位置后,将新结点作为叶子结点插入,经过9次的插入操作,建成由这9个结点组成的二叉排序树。
创建二叉排序树的算法如下:
22 Bin_Sort_Tree_Linklist*bt_bulid(Bin_Sort_Tree a,int n)
23 {∥在数组a 的a [1]~a[n ]
24 单元中存放着将要构成二叉排序树的n 个结点内容
25 bt=NULL;
26 for (i=1;i
27 {
28 p=(Bin_Sort_Tree_Linklist*)malloc
29 (sizeof (Bin_Sort_Tree_Linklist));
30 p->key=a[i ].key;
31 p->otherelem=a[i ].otherelem;;
32 p->lchile=NULL;
33 p->rchile=NULL;
34 bt_insert1(&bt,p);
35 }
36 return (bt);
37 }
(4)二叉排序树的删除
下面分四种情况讨论一下如何确保从二叉树中删除一个结点后,不会影响二叉排序树的性质:
①若要删除的结点p 为叶子结点,可以直接进行删除。
②若要删除结点p 有右子树,但无左子树,可用其右子树的根结点取代要删除结点的位置。
③若要删除结点p 有左子树,但无右子树,可用其左子树的根结点取代要删除结点的位置,与步骤②类似。
④若要删除结点p 的左右子树均非空,则在其左子树中找到最右结点r 来代替被删的结点(即将r 所指结点的右指针置成p 所指结点的右子树的根,然后用p 所指结点的左子树的根结点代替被删的p 所指结点) 。
可以按下述方法来查找到左子树中的最大元素值的结点:首先移动到子树的根,然后沿着各节点的右孩子指针移动,直到右孩子指针为0为止,此时找到的结点即为左子树中的最大元素值的结点。
下面是二叉排序树删除算法的实现:
38 void delnode(btree*b,int x)
39 {
40 btree*p,*q,*r,*t;
41 p=b;
42 q=NULL;
43 while (p!=NULL && p->data!=x)
44 if (x
data)
45 {
46 q=p;
47 p=p->left;
48 }
49 else
50 {
51 q=p;
52 p=p->right;
53 }
54 if (p==NULL)pintf("未发现数据域为%d的结点\\n",x);
55 else if (p->left==NULL)
56 {
57 if (q==NULL)t=p->right;
58 else if (q->left==p)q->left=p->right;
59 else q->right=p->right;
60 }
61 else /*被删结点有左子树*/
62 /*查找被删结点左子树中的最右结点,即刚好小于x 的结点*/
63 {
64 r=p->left;
65 while (r-right!=NULL)
66 r=r-right;
67 /*被删结点的右子树作为r 的右子树*/
68 r-right=p->right;
69 /*被删结点的左子树根代替被删结点*/
70 if (q==NULL)t=p->left; /*被删结点是根结点*/
71 else if (q->left==p)q->left=p->left;
72 else q->right=p->left;
73 }
74 } 【习题及解析】
【例3-34】 二叉树为二叉排序树的()条件是其任一结点的值均大于其左孩子的值,小于其右孩子的值。
A. 充分不必要 B.必要不充分 C.充分必要 D.既不充分也不必要
【分析】 本题考查二叉排序树的定义。由二叉排序树的定义可知,在二叉排序树中,任一非终端结点的关键字的值大于其左子树中所有结点的关键字的值(当然也大于其左孩子的值),小于右子树中所有结点的关键字的值(当然也小于其右孩子的值)。分析至此,我们知道该题的选项中至少应该包括必要条件,故A 、D 可以排除。下面我们看是否充分,也即满足题干要求的二叉树是否一定是二叉排序树。我们可以用反证法,举出一个例子证明满足题干要求的二叉树不一定是二叉排序树。图3-39所示的二叉树满足题干的要求,54,46,但显然这不是一棵二叉排序树,其中序遍历序列为4,6,5,并非一个单增或单减的序列。故正确答案中不应包含充分条件,排除C 。所以本题应该选B 。
【解答】 B。
【例3-35】 设数据集合d={1,12,5,8,3,10,7,13,9},试完成下列各题:
①依次取d 中各数据,构造一棵二叉排序树bt 。
②如何依据此二叉树bt 得到d 的一个有序序列。
③画出在二叉树bt 中删除
"12" 后的树结构。
【分析】 本题考查的是有关二叉排序树的基本概念。其中①考查二叉排序树的生成和插入;②考查如何根据二叉排序树得到有序序列,即二叉排序树中序遍历;
③考查二叉排序树的删除,且是删除结点中被删结点左右子树都非空的情形。
依照前面介绍的二叉排序树的删除结点的规则,删除结点12时应先将其左子树中最右结点(结点10) 的右指针指向其右子树的根结点
(结点13) ,即将结点10的右指针指向结点13,然后用其左子树的根(结点5) 来替代被删除的结点12,即将结点1的右指针指向结点5。
【解答】 ①构造二叉排序树的过程如图3-40所示。
②d的有序序列为bt 的中序遍历次序,即1,3,5,7,8,9,10,12,13。
③删除"12" 后树结构如图3-41所示。
【例3-36】 已知一棵二叉排序树,其结构如图3-42a 所示。画出依次删除关键字为a1=13,a2=12,a3=4,a4=8的各个结点后,该二叉排序树的结构。
【分析】 本题主要考查知识点二叉排序树的删除。其中a1=13为叶结点,可以直接删除,即将其父结点指向该结点的指针置为空。a2=12和a4=8结点为左右子树均非空的结点,删除的时候需将其左子树中的最右结点替代被删除的结点。a3=4结点为左子树为空,右子树非空的结点,删除时需用右子树的根结点替代被删除的结点。
【解答】 依次删除关键字为a1=13,a2=12,a3=4,a4=8的各个结点后,该二叉排序树的变化情况如图3-42所示。
二叉排序树的插入和建立(通过插入实现)算法不考虑树的平衡问题,因此有可能产生高度不平衡的二叉排序树,极端情况下将退化成一个单链表,如图3-43所示,失去了二叉排序树的优点。因此在二叉排序树的插入过程中必须调整树的结构,使之平衡化。为此我们引入平衡二叉树(AVL树) 的概念。
二叉排序树
1.二叉排序树定义
二叉排序树(Binary Sort Tree)或者是一棵空树;或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于根结点的值;若右子树不空,则右子树上所有结点的值均大于根结点的值。
(2)左右子树也都是二叉排序树,如图6-2所示。
2.二叉排序树的查找过程
由其定义可见,二叉排序树的查找过程为:
(1)若查找树为空,查找失败。
(2)查找树非空,将给定值key 与查找树的根结点关键码比较。
(3)若相等,查找成功,结束查找过程,否则:
① 当给值key 小于根结点关键码,查找将在以左孩子为根的子树上继续进行,转(1)。 ② 当给值key 大于根结点关键码,查找将在以右孩子为根的子树上继续进行,转(1)。
3.二叉排序树插入操作和构造一棵二叉排序树
向二叉排序树中插入一个结点的过程:设待插入结点的关键码为key ,为将其插入,先要在二叉排序树中进行查找,若查找成功,按二叉排序树定义,该插入结点已存在,不用插入;查找不成功时,则插入之。因此,新插入结点一定是作为叶子结点添加上去的。构造一棵二叉排序树则是逐个插入结点的过程。对于关键码序列为:{63,90,70,55,67,42,98,83,10,45,58},则构造一棵二叉排序树的过程如图6-3所示。
4.二叉排序树删除操作
从二叉排序树中删除一个结点之后,要求其仍能保持二叉排序树的特性。
设待删结点为*p(p 为指向待删结点的指针),其双亲结点为*f,删除可以分三种情况,如图6-4所示。
(1)*p结点为叶结点,由于删去叶结点后不影响整棵树的特性,所以,只需将被删结点的双亲结点相应指针域改为空指针,如图6-4(a )所示。
(2)*p结点只有右子树或只有左子树,此时,只需将或替换*f结点的*p子树即可,如图6-4(b )、(c )所示。
(3)*p结点既有左子树又有右子树,可按中序遍历保持有序地进行调整,如图6-4(d )、(e )所示。
设删除*p结点前,中序遍历序列为:
① P为F 的左子女时有:…,Pi 子树,P ,Pj ,S 子树,Pk ,Sk 子树,…,P2,S2子树,P1,S1子树,F ,…。
② P 为F 的右子女时有:…,F ,Pi 子树,P ,Pj ,S 子树,Pk ,Sk 子树,…,P2,S2子树,P1,S1子树,…。
则删除*p结点后,中序遍历序列应为:
① P 为F 的左子女时有:…,Pi 子树,Pj ,S 子树,Pk ,Sk 子树,…,P2,S2子树,P1,S1子树,F ,…。
② P为F 的右子女时有:…,F ,Pi 子树,Pj ,S 子树,Pk ,Sk 子树,…,P2,S2子树,
P1,S1子树,…。
有两种调整方法:
① 直接令Pi 为*f相应的子树,以Pr 为Pi 中序遍历的最后一个结点Pk 的右子树。 ② 令*p结点的直接前驱Pr 或直接后继(对Pi 子树中序遍历的最后一个结点Pk )替换*p结点,再按(2)的方法删去Pr 或Pk 。
【算法分析】
对给定序列建立二叉排序树,若左右子树均匀分布,则其查找过程类似于有序表的折半查找。但若给定序列原本有序,则建立的二叉排序树就蜕化为单链表,其查找效率和顺序查找一样。因此,对均匀的二叉排序树进行插入或删除结点后,应进行调整,使其依然保持均匀。
3.2.5 二叉排序树
在这一部分我们要掌握的是二叉排序树的概念、查找、插入和删除操作。在以下的知识点中,二叉排序树的删除相对于其他知识点要复杂一些,但只要掌握了规则,题目还是很容易解决的。下面我们先分别给出各部分的介绍及算法实现,再对一些典型题目进行解析。
(1)二叉排序树
二叉排序树是一种常用的动态查找表,下面首先给出它的非递归形式。
二叉排序树是一棵二叉树,它或者为空,或者具有如下性质:
①任一非终端结点若有左孩子,则该结点的关键字值大于其左孩子结点的关键字值。 ②任一非终端结点若有右孩子,则该结点的关键字值小于其右孩子结点的关键字值。 二叉排序树也可以用递归的形式定义,即二叉排序树是一棵树,它或者为空,或者具有如下性质:
①若它的左子树非空,则其左子树所有结点的关键字值均小于其根结点的关键字值。 ②若它的右子树非空,则其右子树所有结点的关键字值均大于其根结点的关键字值。 ③它的左右子树都是二叉排序树。
例如,由关键字值序列(62,15,68,46,65,12,57,79,35)构成的一棵二叉排序树如图3-38所示。
如果对上述二叉排序树进行中序遍历可以得到一个关键字有序序列(12,15,35,46,57,62,65,68,79),这是二叉排序树的一个重要特征,也正是由此将其称为"
二叉排序树" 。
(2)二叉排序树的查找
二叉排序树的结构定义中可看到:一棵非空二叉排序树中根结点的关键字值大于其左子树上所有结点的关键字值,而小于其右子树上所有结点的关键字值。因此在二叉排序树中查找一个关键字值为k 的结点的基本思想是:用给定值k 与根结点关键字值比较,如果k 小于根结点的值,则要找的结点只可能在左子树中,所以继续在左子树中查找,否则将继续在右子树中查找,依此方法,查找下去,至直查找成功或查找失败为止。二叉排序树查找的过程描述如下:
①若二叉树为空树,则查找失败;
②将给定值k 与根结点的关键字值比较,若相等,则查找成功;
③若根结点的关键字值小于给定值k ,则在左子树中继续搜索;
④否则,在右子树中继续查找。
假定二叉排序树的链式存储结构的类型定义如下:
1
2
3
4
5
6 typedef struct linklist{ keytype key; anytype otherelem; struct linklist*lchild; struct linklist*rchild; }Bin_Sort_Tree_Linklist,*Bin_Sort_Tree;
二叉排序树查找过程的描述是一个递归过程,若用链式存储结构存储,其查找操作的递归算法如下所示:
7
8
9 Bin_Sort_Tree_Linklist*bt_search(Bin_Sort_Tree bt,keytype k) {∥在根指针为bt 的二叉排序树上查找一个关键字值为k 的结点, ∥若查找成功返回指向该结点的指针,否则返回空指针
10 if (bt=NULL)‖(bt->key==k)return bt;
11 else if (kkey)return bt_search(bt->lchild,k);
12 ∥在左子树中搜索
13 else return bt_search(bt->rchild,k);//在右子树中搜索
14 }
这个过程也可以用非递归算法实现,算法描述如下:
15 Bin_Sort_Tree_Linklist*bt_search1(Bin_Sort_Tree bt,keytype k)
16 {
17 p=bt;∥指针p 指向根结点,搜索从根结点开始
18 while (p!=NULL && p->key!=k)
19 {
20 if (k
key)p=p->lchild;
21 else p=p->rchild;
22 }
23 return (p);
24 } 3)二叉排序树的插入
在一棵二叉排序树中插入一个结点可以用一个递归的过程实现。若二叉排序树为空,则新结点作为二叉排序树的根结点。否则,若给定结点的关键字值小于根结点关键字值,则插入在左子树上;若给定结点的关键字值大于根结点的值,则插入在右子树上。
下面是二叉排序树插入操作的递归算法。
1
2
3
4
5
6 void bt_insert1(Bin_Sort_Tree*bt,Bin_Sort_Tree_Linklist*pn) {∥在以bt 为根的二叉排序树上插入一个由指针pn 指向的新的结点 if (*bt=NULL)*bt=pn; else if (*bt->key>pn->key)bt_insert 1(&(*bt->lchild),pn); else if (*bt->keykey)bt_insert1(&(*bt->rchild),pn); }
这个算法也可以用非递归的形式实现,如下所示:
7
8
9 void bt_insert 2(Bin_Sort_Tree*bt, Bin_Sort_Tree_Linklist*pn) {
10 p=bt;
11 while (p!=NULL && p->key!=pn->key)
12 {
13 q=p;
14 if (p->key>pn->key)p=p->lchild;
15 else p=p->rchild;
16 }
17 if (p=NULL){
18 if (q->key>pn->key)q->lchild=pn;
19 else q->rchild=pn->key;
20 }
21 } 利用二叉排序树的插入算法,可以很容易地实现创建二叉排序树的操作,其基本思想为:由一棵空二叉树开始,经过一系列的插入操作生成一棵二叉排序树。
例如,由结点关键字序列(62,15,68,46,65,12,57,79,35)构造二叉排序树的过程为:从空二叉树开始,依次将每个结点插入到二叉排序树中,在插入每个结点时都是从根结点开始搜索插入位置,找到插入位置后,将新结点作为叶子结点插入,经过9次的插入操作,建成由这9个结点组成的二叉排序树。
创建二叉排序树的算法如下:
22 Bin_Sort_Tree_Linklist*bt_bulid(Bin_Sort_Tree a,int n)
23 {∥在数组a 的a [1]~a[n ]
24 单元中存放着将要构成二叉排序树的n 个结点内容
25 bt=NULL;
26 for (i=1;i
27 {
28 p=(Bin_Sort_Tree_Linklist*)malloc
29 (sizeof (Bin_Sort_Tree_Linklist));
30 p->key=a[i ].key;
31 p->otherelem=a[i ].otherelem;;
32 p->lchile=NULL;
33 p->rchile=NULL;
34 bt_insert1(&bt,p);
35 }
36 return (bt);
37 }
(4)二叉排序树的删除
下面分四种情况讨论一下如何确保从二叉树中删除一个结点后,不会影响二叉排序树的性质:
①若要删除的结点p 为叶子结点,可以直接进行删除。
②若要删除结点p 有右子树,但无左子树,可用其右子树的根结点取代要删除结点的位置。
③若要删除结点p 有左子树,但无右子树,可用其左子树的根结点取代要删除结点的位置,与步骤②类似。
④若要删除结点p 的左右子树均非空,则在其左子树中找到最右结点r 来代替被删的结点(即将r 所指结点的右指针置成p 所指结点的右子树的根,然后用p 所指结点的左子树的根结点代替被删的p 所指结点) 。
可以按下述方法来查找到左子树中的最大元素值的结点:首先移动到子树的根,然后沿着各节点的右孩子指针移动,直到右孩子指针为0为止,此时找到的结点即为左子树中的最大元素值的结点。
下面是二叉排序树删除算法的实现:
38 void delnode(btree*b,int x)
39 {
40 btree*p,*q,*r,*t;
41 p=b;
42 q=NULL;
43 while (p!=NULL && p->data!=x)
44 if (x
data)
45 {
46 q=p;
47 p=p->left;
48 }
49 else
50 {
51 q=p;
52 p=p->right;
53 }
54 if (p==NULL)pintf("未发现数据域为%d的结点\\n",x);
55 else if (p->left==NULL)
56 {
57 if (q==NULL)t=p->right;
58 else if (q->left==p)q->left=p->right;
59 else q->right=p->right;
60 }
61 else /*被删结点有左子树*/
62 /*查找被删结点左子树中的最右结点,即刚好小于x 的结点*/
63 {
64 r=p->left;
65 while (r-right!=NULL)
66 r=r-right;
67 /*被删结点的右子树作为r 的右子树*/
68 r-right=p->right;
69 /*被删结点的左子树根代替被删结点*/
70 if (q==NULL)t=p->left; /*被删结点是根结点*/
71 else if (q->left==p)q->left=p->left;
72 else q->right=p->left;
73 }
74 } 【习题及解析】
【例3-34】 二叉树为二叉排序树的()条件是其任一结点的值均大于其左孩子的值,小于其右孩子的值。
A. 充分不必要 B.必要不充分 C.充分必要 D.既不充分也不必要
【分析】 本题考查二叉排序树的定义。由二叉排序树的定义可知,在二叉排序树中,任一非终端结点的关键字的值大于其左子树中所有结点的关键字的值(当然也大于其左孩子的值),小于右子树中所有结点的关键字的值(当然也小于其右孩子的值)。分析至此,我们知道该题的选项中至少应该包括必要条件,故A 、D 可以排除。下面我们看是否充分,也即满足题干要求的二叉树是否一定是二叉排序树。我们可以用反证法,举出一个例子证明满足题干要求的二叉树不一定是二叉排序树。图3-39所示的二叉树满足题干的要求,54,46,但显然这不是一棵二叉排序树,其中序遍历序列为4,6,5,并非一个单增或单减的序列。故正确答案中不应包含充分条件,排除C 。所以本题应该选B 。
【解答】 B。
【例3-35】 设数据集合d={1,12,5,8,3,10,7,13,9},试完成下列各题:
①依次取d 中各数据,构造一棵二叉排序树bt 。
②如何依据此二叉树bt 得到d 的一个有序序列。
③画出在二叉树bt 中删除
"12" 后的树结构。
【分析】 本题考查的是有关二叉排序树的基本概念。其中①考查二叉排序树的生成和插入;②考查如何根据二叉排序树得到有序序列,即二叉排序树中序遍历;
③考查二叉排序树的删除,且是删除结点中被删结点左右子树都非空的情形。
依照前面介绍的二叉排序树的删除结点的规则,删除结点12时应先将其左子树中最右结点(结点10) 的右指针指向其右子树的根结点
(结点13) ,即将结点10的右指针指向结点13,然后用其左子树的根(结点5) 来替代被删除的结点12,即将结点1的右指针指向结点5。
【解答】 ①构造二叉排序树的过程如图3-40所示。
②d的有序序列为bt 的中序遍历次序,即1,3,5,7,8,9,10,12,13。
③删除"12" 后树结构如图3-41所示。
【例3-36】 已知一棵二叉排序树,其结构如图3-42a 所示。画出依次删除关键字为a1=13,a2=12,a3=4,a4=8的各个结点后,该二叉排序树的结构。
【分析】 本题主要考查知识点二叉排序树的删除。其中a1=13为叶结点,可以直接删除,即将其父结点指向该结点的指针置为空。a2=12和a4=8结点为左右子树均非空的结点,删除的时候需将其左子树中的最右结点替代被删除的结点。a3=4结点为左子树为空,右子树非空的结点,删除时需用右子树的根结点替代被删除的结点。
【解答】 依次删除关键字为a1=13,a2=12,a3=4,a4=8的各个结点后,该二叉排序树的变化情况如图3-42所示。
二叉排序树的插入和建立(通过插入实现)算法不考虑树的平衡问题,因此有可能产生高度不平衡的二叉排序树,极端情况下将退化成一个单链表,如图3-43所示,失去了二叉排序树的优点。因此在二叉排序树的插入过程中必须调整树的结构,使之平衡化。为此我们引入平衡二叉树(AVL树) 的概念。