深入学习二叉树(四) 二叉排序树

1 前言

数据结构中,线性表分为无序线性表和有序线性表。
无序线性表的数据是杂乱无序的,所以在插入和删除时,没有什么必须遵守的规则,可以插入在数据尾部或者删除在数据尾部。但是在查找的时候,需要遍历整个数据表,导致无序线性表的查找效率低。
有序线性表的数据则相反,查找数据时的时候因为数据是有序的,可以用二分法、插值法、斐波那契查找法来实现。但是,当进行插入和删除操作时,需要维护表中数据的有序性,会耗费大量的时间。
那么,我们希望找到一种数据结构,既可以有较高的插入和删除效率,并且具备较高的查找效率,因此,二叉排序树应运而生。

2 二叉排序树

2.1 定义

二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),也称二叉搜索树。二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:

(1)若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
(3)左、右子树也分别为二叉排序树;

2.2 构造一棵二叉排序树

现有序列:61 87 59 47 35 73 51 98 37 93

构造过程如下:
1)索引 i = 0,A[i] = 61,结点61作为根结点,如图2.1:

2)索引 i = 1,A[1] = 87, 87 > 61,且结点61右孩子为空,故81为61结点的右孩子,如图2.2:

3)索引 i = 2,A[i] = 59,59 <61,且结点61左孩子为空,故59为61结点的左孩子,如图2.3:

4)索引 i = 3,A[3] = 47,47 < 59,且结点59左孩子为空,故47为59结点的左孩子,如图2.4:

5)索引 i = 4,A[4] = 35,35 < 47,且结点47左孩子为空,故35为47结点的左孩子,如图2.5:

采用同样规则遍历整个数组得到如图2.6所示的一棵排序二叉树。

2.3 二叉排序树查找

由二叉树的递归定义性质,二叉排序树的查找同样可以使用如下递归算法查找。

如果树是空的,则查找结束,无匹配。
如果被查找的值和根结点的值相等,查找成功。否则就在子树中继续查找。如果被查找的值小于根结点的值就选择左子树,大于根结点的值就选择右子树。

在理想情况下,每次比较过后,树会被砍掉一半,近乎折半查找。
遍历打印可以使用中序遍历,打印出来的结果是从小到大的有序数组。
查找代码:

typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ /* 二叉树的二叉链表结点结构定义 */
typedef  struct BiTNode /* 结点结构 */
{int data;   /* 结点数据 */struct BiTNode *lchild, *rchild;    /* 左右孩子指针 */
} BiTNode, *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);  /*  在右子树中继续查找 */
}

对于图2.6所示的二叉排序树,若查找结点key为47则可以查找成功,若查找结点key为75,树中不存在key为75的结点,故查找失败,则查找指针p指向查找路径的最后一个结点,即结点73。

2.4 二叉排序树插入

二叉排序的插入是建立在二叉排序的查找之上的,插入一个结点,就是通过查找发现该结点合适插入位置,把结点直接放进去。 其实在2.2节中一步步构造二叉排序树的过程中就是结点插入过程。由此可以得出二叉排序树插入规则如下:

若查找的key已经有在树中,则p指向该数据结点。
若查找的key没有在树中,则p指向查找路径上最后一个结点。

例如:若在图2.6展示的二叉排序树中插入结点数据为60的结点。
首先查找结点数据为60的结点,二叉排序树中不存在结点为60的结点,因此查找失败。此时查找指针p指向查找路径最后一个结点即指向59结点。由于60>59且59结点右子树为空,故将60结点作为59结点的右孩子,插入完成。插入后的二叉排序树如图2.8所示。


插入代码:

struct BiTree {int data;BiTree *lchild;BiTree *rchild;
};//在二叉排序树中插入查找关键字key
BiTree* InsertBST(BiTree *t,int key)
{if (t == NULL){t = new BiTree();t->lchild = t->rchild = NULL;t->data = key;return t;}if (key < t->data) t->lchild = InsertBST(t->lchild, key);elset->rchild = InsertBST(t->rchild, key);return t;
}//n个数据在数组d中,tree为二叉排序树根
BiTree* CreateBiTree(BiTree *tree, int d[], int n)
{for (int i = 0; i < n; i++)tree = InsertBST(tree, d[i]);
}

2.5 二叉排序树删除

二叉树的删除可不再像二叉树的插入那么容易了,以为删除某个结点以后,会影响到树的其它部分的结构。
删除的时候需要考虑以下几种情况:

1)删除结点为叶子结点;
2)删除的结点只有左子树;
3)删除的结点只有右子树
4)删除的结点既有左子树又有右子树。

考虑前三种情况,处理方式比较简单。
例如:若要删除图2.8中的结点93,则直接删除该结点即可。删除后二叉排序树如图2.9所示:

若要删除的结点为结点35,结点35只有右子树,只需删除结点35,将右子树37结点替代结点35即可。删除后的二叉排序树如图2.10所示:

删除只有左子树的结点与此情况类似。

情况4相对比较复杂,对于待删除结点既有左子树又有右子树的情形,最佳办法是在剩余的序列中找到最为接近的结点来代替删除结点。这种代替并不会影响到树的整体结构。那么最为接近的结点如何获取呢?
可以采用中序遍历的方式来得到删除结点的前驱和后继结点。选取前驱结点或者后继结点代替删除结点即可。
例如:待删除的结点为47,图2.8中二叉排序树的中序遍历序列为35 37 47 51 59 60 61 73 87 93 98。则结点47的前驱结点为37,则直接将37结点替代47结点即可。替换后的二叉排序树如图2.11所示:

删除代码:

/* 若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素结点, */
/* 并返回TRUE;否则返回FALSE。 */
Status DeleteBST(BiTree *T,int key)
{ if(!*T) /* 不存在关键字等于key的数据元素 */ return FALSE;else{if (key==(*T)->data) /* 找到关键字等于key的数据元素 */ return Delete(T);else if (key<(*T)->data)return DeleteBST(&(*T)->lchild,key);elsereturn DeleteBST(&(*T)->rchild,key);}
}
/* 从二叉排序树中删除结点p,并重接它的左或右子树。 */
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; /*  s指向被删结点的直接前驱(将被删结点前驱的值取代被删结点的值) */if(q!=*p)q->rchild=s->lchild; /*  重接q的右子树 */ elseq->lchild=s->lchild; /*  重接q的左子树 */free(s);}return TRUE;
}

3 结语

二叉排序树是一种查找与插入效率均较为高效的数据结构,同时,二叉排序树也是二叉树学习中的重点与难点。希望通过本篇的学习能够掌握二叉排序树的查找、插入与删除等基本操作,也希望读者给出指导意见。

深入学习二叉树(四) 二叉排序树相关推荐

  1. Java开发 - 树(二叉树,二叉排序树,红黑树)

    目录 前言 你好,认识一下,我是树 二叉树与二叉排序树 二叉排序树特点 为什么说二叉排序树查询效率要高于链表呢? 元素的类型 比较器 手写二叉排序树 定义一棵二叉树 增加元素 查询元素 修改元素 删除 ...

  2. C1认证学习十四、十五(算法常识、HTML Head 头)

    C1认证学习十四.十五(算法常识.HTML Head 头) 文章目录 C1认证学习十四.十五(算法常识.HTML Head 头) 十四(算法常识) 任务背景 任务目标 查找算法 1.顺序查找 2.二分 ...

  3. Java IO流学习总结四:缓冲流-BufferedReader、BufferedWriter

    Java IO流学习总结四:缓冲流-BufferedReader.BufferedWriter 转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/det ...

  4. 强化学习(四) - 蒙特卡洛方法(Monte Carlo Methods)及实例

    强化学习(四) - 蒙特卡洛方法(Monte Carlo Methods)及实例 4. 蒙特卡洛方法 4.1 蒙特卡洛预测 例4.1:Blackjack(21点) 4.2 动作价值的蒙特卡洛估计 4. ...

  5. Maven学习总结(四)——Maven核心概念

    2019独角兽企业重金招聘Python工程师标准>>> Maven学习总结(四)--Maven核心概念 一.Maven坐标 1.1.什么是坐标? 在平面几何中坐标(x,y)可以标识平 ...

  6. 【转】MyBatis学习总结(四)——解决字段名与实体类属性名不相同的冲突

    [转]MyBatis学习总结(四)--解决字段名与实体类属性名不相同的冲突 在平时的开发中,我们表中的字段名和表对应实体类的属性名称不一定都是完全相同的,下面来演示一下这种情况下的如何解决字段名与实体 ...

  7. C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻

    前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...

  8. 四、Android学习第四天——JAVA基础回顾(转)

    (转自:http://wenku.baidu.com/view/af39b3164431b90d6c85c72f.html) 四.Android学习第四天--JAVA基础回顾 这才学习Android的 ...

  9. IOS学习笔记(四)之UITextField和UITextView控件学习

    IOS学习笔记(四)之UITextField和UITextView控件学习(博客地址:http://blog.csdn.net/developer_jiangqq) Author:hmjiangqq ...

最新文章

  1. Linux中look命令,如何在Linux上使用look命令 | MOS86
  2. C++语言之继承类型
  3. JBuilder9+Weblogic8.1——Filter过滤器在Jbuilder9中的运用
  4. HotSpot增量Java垃圾收集器
  5. 前端学习(1648):前端系列实战课程之任务功能清单思路
  6. c# wpf 面试_【远程面试】九强通信 | 九洲电器集团全资子公司
  7. MySQL笔记-事务理论及并发存在的三个问题(脏读、不可重复读、幻读)演示
  8. 逛知乎才知网站建设不只有wordpress建站系统,小白值得收藏
  9. 【Datapump】expdp和impdp中parallel参数解释
  10. 川大NLP博士生被华为以200万年薪录用!分享以下科研及论文写作经验
  11. 关于android集成开发环境-引入jar包错误的问题
  12. 常用SFTP工具类(多个公用方法)
  13. cydia未能连接服务器超时,cydia添加源验证超时
  14. matlab coder分析
  15. 习题4-9 打印菱形图案(15分)
  16. r语言赋值为na_如何将R数据中的NA值替换为0?
  17. springboot+vue网上书城书店销售商城系统
  18. 去除IntelliJ IDEA中重复代码报灰黄色的下划波浪线
  19. 学习——Regression Tree 回归树
  20. win98计算机系统硬件配置,Windows98操作系统优化实战(1) -电脑资料

热门文章

  1. [转载] Python中str跟int的转换
  2. java scanner_Java Scanner skip()方法与示例
  3. ofb模式_密码学中的输出反馈模式(OFB)
  4. Java ObjectOutputStream writeLong()方法与示例
  5. inventor扳手制作视频_弱电工程视频监控系统施工方案,可作施工组织设计
  6. Java FilePermission暗含()方法与示例
  7. 为什么ConcurrentHashMap不允许插入null值?
  8. 这8种常见的SQL错误用法,你还在用吗?
  9. 面试官:HTTPS 为什么是安全的?说一下他的底层实现原理?
  10. 华为交换机基本查询、目录、文件操作命令