数据结构和算法分析学习笔记(三)--二叉查找树的懒惰删除(lazy deletion)
这次的问题来自《数据结构与算法分析(C++描述)》的习题4.16,如下:
--------------------------
4.16 重做二叉查找树类以实现懒惰删除.注意,这将影响所有的例程.特别具有挑战性的是findMin和findMax,它们现在必须递归的完成.
--------------------------
这题没有参考答案,我也不敢保证自己写的是对的,有不对的地方请指正,谢谢.先做一下说明,首先这只是一般的二叉查找树,不是AVL树.其次其中的printTree()函数只是将树中的结点按升序打印出来,不是像树的结构那样打印(可以参见这里的print()函数).最后,我定义的数据结构是在有重复项的情况下使用的.也就是说,每一个结点有一个记录数据出现次数的成员count(非负整数).当第一次插入某一数据时,count为1,以后每插入一次这一数据则它的count加1.而当某一数据没有被实际删除且它的count大于0时,对它的删除操作就是将它的count减1.当逻辑上被删除的结点(count值为0的结点)的数目达到实际上存在的总结点数目的一半时,就对二叉查找树进行真正的删除操作(delete).
下面在课本中给出的一般二叉查找树结构(参见书中图4-16至图4-28)的基础上进行修改,得到我们所需要的结构.首先自然是结点的结构,如前所述,需要对结点BinaryNode类增加一个记录数据出现次数的成员count.而在BSTree类中,我们需要两个int型的私有成员分别统计树的总结点数和其中在逻辑上已经被删除的结点数.接下来是contains(),makeEmpty(),printTree()和insert(),这些都比较简单.
我与课本上makeEmpty( BinaryNode * & t )(置空操作)的不同是,我没有在最后将节点置为NULL,而是在调用这个函数之后将实参置为NULL,不知道这样做会不会有什么错误.对于insert函数,我们可以看到这种结构的好处,对于那些仅在逻辑上被删除的数据,重新插入它只需要将count加1并且将delSize减1.
1 template <typename Object> 2 class BSTree 3 { 4 public: 5 BSTree( ){theSize=delSize=0;root=NULL;} 6 ~BSTree( ) 7 { 8 makeEmpty(root); 9 theSize=delSize=0;10 }11 12 bool contains( const Object & x ) const//是否包含某值13 {return contains(x,root);}14 bool isEmpty( ) const//是否为空15 {return theSize-delSize==0;}16 void printTree( ) const//打印树17 {printTree(root);}18 19 void makeEmpty( )//置空20 {21 makeEmpty(root);22 theSize=delSize=0;root=NULL;23 }24 void insert( const Object & x )//插入值25 {insert(x,root);}26 27 private:28 struct BinaryNode29 {30 Object element;//Object类型的值31 BinaryNode *left;//左子树32 BinaryNode *right;//右子树33 int count; //计数34 35 BinaryNode( const Object & theElement, BinaryNode *lt, BinaryNode *rt ,int c=1)//第一次插入值为136 : element( theElement ), left( lt ), right( rt ), count(c) { }37 };38 39 BinaryNode *root;//根结点40 int theSize;41 int delSize;42 43 void insert( const Object & x, BinaryNode * & t ) ;44 45 bool contains( const Object & x, BinaryNode *t ) const;46 void makeEmpty( BinaryNode * & t );47 void printTree( BinaryNode *t ) const48 {49 if(t)50 {51 printTree(t->left);52 if(t->count>0)std::cout<<t->element<<" ";53 printTree(t->right);54 }55 }56 };
1 template <typename Object> 2 bool BSTree<Object>::contains( const Object & x, BinaryNode *t ) const 3 { 4 if(!t)return false; 5 if(x<t->element) 6 return contains(x,t->left); 7 else if(x>t->element) 8 return contains(x,t->right); 9 else if(t->count>0)10 return true;11 else12 return false;13 }14 15 template <typename Object>16 void BSTree<Object>::makeEmpty( BinaryNode * & t )17 {18 if(t)19 {20 makeEmpty(t->left);21 makeEmpty(t->right);22 delete t;23 }24 }25 26 template <typename Object>27 void BSTree<Object>::insert( const Object & x, BinaryNode * & t )28 {29 if(t==NULL)30 {31 ++theSize;32 t=new BinaryNode(x,NULL,NULL);//count默认为133 }34 else if(x<t->element)35 insert(x,t->left);36 else if(x>t->element)37 insert(x,t->right);38 else if(t->count++==0)39 --delSize;//如果count的值原本为0,则将其加1的同时要将delSize减1.40 }
接下来是remove操作,现在remove也显得代价更小,当删除某项数据时,只需要将它的count减1而不需要从树真正删除它.而当它的count为0时,则需要将delSize加1以表示又有一个结点在逻辑上已经被删除.最后再检查delSize是否达到了theSize的一半.如果达到,则执行真正的delete操作.
1 template <typename Object> 2 void BSTree<Object>::remove( const Object & x, BinaryNode * & t ) 3 { 4 if(t==NULL)return; 5 if(x<t->element) 6 remove(x,t->left); 7 else if(x>t->element) 8 remove(x,t->right); 9 else if(t->count>0&&--(t->count)==0&&(++delSize>=theSize-delSize)) 10 //若count大于0,则减1;若减1后为0,则delSize加1;11 //若delSize达到theSize的一半,则执行delete操作12 {13 delete_nodes(root);//真正的删除操作14 theSize-=delSize;//更新总结点数15 delSize=0;//delSize归零16 }17 }
现在的问题是delete_nodes( BinaryNode * & t )例程的实现,我们要删除树中所有count为0的结点.我这里使用了递归的方法,如果某个结点不必删除,那么就继续对它的左子树和右子树执行delete_nodes().回想一般二叉查找树的删除,对于叶子结点,直接删除即可,这在delete_nodes()中也是一样.而对于只有一个非空子树的结点,直接删除后将它的非空子树"拼接"上即可,但这时需要对拼接上的子树继续执行delete_nodes().
最后就是删除那些两个子树都非空的结点,我们还是在它的右子树上找到一个值最小的结点(注意:要在count大于0的结点中找),我们假设这个点为min.然后将t的值和count值都替换成min相应的值,同时将min的count值修改为0以保证稍后删除.最后,继续对t的左子树和右子树执行delete_nodes()操作.这里面一个可能存在的情况是t的右子树中所有结点的count值都为0,那么我们可以将它的右子树置空,这样t就变成了一个只有一个非空子树的结点.
在实际的编程中,我觉得比较费脑筋的就是findMin()和findMax()例程.以findMin(BinaryNode *t)为例,首先要找到t中的最小值,然后再升序一步步往上寻找第一个count大于0的结点.我采用的递归的方法,这一例程返回bool值,false表示没有找到最小值,也就意味着t为空或者t中所有结点的count均为0.而为了保存查找到的最小(最大)结点,我给BSTree类增加了两个私有成员,两个BinaryNode *类型的指针min和max.另外,我还提供了两个公有的函数,分别返回整颗树的最小和最大值,但是这两个函数需要在保证整颗树非空的情况下使用.
1 private: 2 BinaryNode *min; 3 BinaryNode *max; 4 5 template <typename Object> 6 bool BSTree<Object>::findMin( BinaryNode *t ) 7 { 8 if(t) 9 {10 if(findMin(t->left))return true;//在t的左子树中找到count大于0的点,查找结束.11 if(t->count>0){min=t;return true;}//找到count大于0的点,查找结束.12 return findMin(t->right);//否则继续查找右子树13 }14 return false;//空结点则返回false15 }16 17 template <typename Object>18 bool BSTree<Object>::findMax( BinaryNode *t)19 {20 if(t)21 {22 if(findMax(t->right))return true;23 if(t->count>0){max=t;return true;}24 return findMax(t->left);25 }26 return false;27 }28 29 template <typename Object>30 void BSTree<Object>::delete_nodes(BinaryNode * & t )31 {32 if(t==NULL)return;//空子树,do nothing33 if(t->count==0)//t需要被删除34 {35 if((t->left!=NULL)&&(t->right!=NULL)&&findMin(t->right))36 //t的两个子树均非空,且在它的右子树中找到了可用的最小结点37 {38 t->element=min->element;39 t->count=min->count;40 min->count=0;//右子树中的最小结点即将被删除41 delete_nodes(t->left);42 delete_nodes(t->right);43 }44 else45 {46 if((t->left!=NULL)&&(t->right!=NULL))47 //t的右子树中的所有结点的count均为0,则将其置空.48 {makeEmpty(t->right);t->right=NULL;} 49 BinaryNode *oldnode=t;50 t=t->left?t->left:t->right;51 delete oldnode;52 if(t!=NULL)delete_nodes(t);//若新的t非空,继续对它执行删除操作53 }54 }55 else//t不需要被删除,继续在它的子树中查找56 {57 delete_nodes(t->left);58 delete_nodes(t->right);59 }60 }61 62 public:63 const Object & findMin( )//返回最小值64 {65 if(!findMin(root))66 std::cerr<<"The tree is Empty!"<<std::endl;67 return min->element;68 }69 70 const Object & findMax( )//返回最大值71 {72 if(!findMax(root))73 std::cerr<<"The tree is Empty!"<<std::endl;74 return max->element;75 }
最后提供我的BSTree.h文件,有不对的地方请指正,谢谢.
BSTree.h
转载于:https://www.cnblogs.com/heqile/archive/2011/12/08/2280120.html
数据结构和算法分析学习笔记(三)--二叉查找树的懒惰删除(lazy deletion)相关推荐
- 数据结构与算法分析学习笔记
@前言 迎来了本学期的重头专业课--数据结构与算法分析. 这次我们的老师是来自英国威尔士亚伯大学(Aberystwyth University)的Fred Long教授!!!老师曾以访问学者的身份来到 ...
- 算法分析学习笔记(二) - 栈和队列(上)
一. 写在前面的话 本篇为"算法分析学习笔记"系列的第二篇,我们来聊一些跟基础数据结构有关的知识.对于网上无数的算法高手来说,这里讨论的东西都太小儿科了,但是作为一个系列的文章,我 ...
- 数据结构与算法学习笔记之 提高读取性能的链表(上)
数据结构与算法学习笔记之 提高读取性能的链表(上) 前言 链表(Linked list)比数组稍微复杂一点,在我们生活中用到最常见的应该是缓存,它是一种提高数据读取性能的技术,常见的如cpu缓存,浏览 ...
- 数据结构与算法-java笔记一 更新中
数据结构与算法-java笔记一 更新中 数据结构与算法 什么是数据结构.算法 数据结构学了有什么用: 线性结构 数组 特点 应用 链表 存储结构 链表类型 单链表 双向链表 双向循环链表 链表与数组的 ...
- 数据结构与算法学习笔记4:递归+分治法
数据结构与算法学习笔记4 递归 斐波那契数列 青蛙跳台阶问题 链表倒序打印 分治法 二分查找/折半查找 Binary Search 题目1:快速幂 题目2:如何判断一个数是否为2的次幂 递归 指在函数 ...
- 【Python数据结构与算法】(三):递归(Recursion)
[Python数据结构与算法](三):递归(Recursion) ✨本文收录于<Python数据结构与算法>专栏,此专栏主要记录如何python学习数据结构与算法笔记.
- 数据结构与算法 学习笔记(5):字符串
数据结构与算法 学习笔记(5)- 字符串 本次笔记记录了LeetCode中关于字符串的一些问题,并给出了相应的思路说明和代码.题目编号与LeetCode对应,方便查找. 题目1:LeetCode 13 ...
- 数据结构与算法学习笔记之先进先出的队列
前言 队列是一种非常实用的数据结构,类似于生活中发排队,可应用于生活,开发中各个方面,比如共享打印机(先请求先打印),消息队列.你想知道他们是怎么工作的么.那就来一起学习一下队列吧 正文 一.队列的定 ...
- 浅谈数据结构与算法分析学习及如何进行算法分析
一.前言 都说数据结构与算法分析是程序员的内功,想要理解计算机世界就不能不懂点数据结构与算法,然而这也备受争议,因为大多数的业务需求都用不上数据结构与算法,又或者说已经有封装好的库可以直接调用,例如J ...
最新文章
- 深度学习与机器学习的思考
- mysql5 7安装教程_MySQL57安装教程
- Weird Flecks, But OK
- 【bzoj1532】[POI2005]Kos-Dicing 二分+网络流最大流
- burst什么意思_为什么Windows/iOS操作很流畅而Linux/Android却很卡顿呢?
- 左右mysql事务提交
- oracle表空间管理图形界面,Oracle表空间管理和优化
- Linux后台运行进程命令
- CVPR 2021奖项出炉:最佳论文花落马普所,何恺明获提名,首届黄煦涛纪念奖颁布
- win10+VS2013+OPENCV如何配置于仕琪人脸检测算法
- 数据结构算法常见面试考题
- ansys 命令流基础—— 点线面体基本操作
- CentOS7安装搜狗输入法
- 解决显示“此图片来自微信公众平台未经允许不可引用”错误图片
- 单片机中存储器扩展位地址线怎么算_单片机程序存储器的扩展
- VUE:element ui组件应用之个人中心页面
- 数据可视化(三)基于 Graphviz 实现程序化绘图
- Zynq-7000电子相册的实现
- 史上最强最逼真的游戏
- 移植 u-boot-2020.07 到 iTOP-4412(二)地址相关码 boot
热门文章
- python gpu编程_Python笔记_第四篇_高阶编程_进程、线程、协程_5.GPU加速
- python双下划线什么意思_python中几个双下划线用法的含义
- 『设计模式』不看就亏了的设计模式总结
- 『数据库』数据库的查询可不是只知道Select就可以的--关系数据库系统的查询处理
- 使用JXL.jar实现JAVA对EXCEL的读写操作
- 图论--网络流最大流问题
- 通过keras例子理解LSTM 循环神经网络(RNN)
- (转载) min()的宏定义中的(void) (_x == _y)的含义
- 删掉被2345篡改的IE起始页
- 一些机器学习数据集(Dataset)