当前位置: 首页 > 编程日记 > 正文

笔试算法题(58):二分查找树性能分析(Binary Search Tree Performance Analysis)

议题:二分查找树性能分析(Binary Search Tree Performance Analysis)

分析:

  • 二叉搜索树(Binary Search Tree,BST)是一颗典型的二叉树,同时任何节点的键值大于等于该节点左子树中的所有键值,小于等于该节点右子树中的所有键值,并且每个节点域中保存 一个记录以其为根节点的子树中所有节点个数的属性,这个属性可用于支持贪婪算法的实现;

  • 二叉搜索树的建立是在树的底部添加新的元素,搜索即从根元素开始到达树底部的一条路径,插入和搜索相似(注意对重复键的处理),排序按照节点访问方式不同有前序、中序、后序三种;

  • 二叉搜索树算法的运行时间取决于树的形状,最好情况下根节点与每个外部节点间有㏒N个节点,此时树完全平衡,最坏情况下搜索路径上有N个节点。由于创建二 叉搜索树的时候,第一个插入的元素总是作为根元素,所以元素插入的顺序决定树的形状。在随机情况下,极度平衡和极度不平衡的树都很少出现,所以这种情况下 二叉搜索树算法有着良好的运行情况;

  • 所以平均情况下,N个随机生成的BST树种,一次搜索,插入大约需要1.39㏒N此比较。如果键值不是随机出现,则二叉搜索树退化为N个节点的链表,一次操作为线性O(N)运行时间;

  • 使用BST树存储文件中每一个文本串,基于字符串的排序使得搜索变得容易;

  • BottomUp插入策略:按照前序策略遍历整个树结构,首先查看当前节点是否为NULL,然后与关键值比较查看是否为目标值,不是的话就分别针对左右子 树递归调用搜索算法,然后进入下一个结构,注意在递归调用之间的衔接是由返回一个节点来实现的,所以如果已经到达树底部,则返回一个新节点,这个节点正好 位于上一级的子树连接上,这样正好形成整个树结构;

  • TopDown插入策略:在BottomUp插入策略的基础上,将新插入的节点在递归回溯的时候逐层旋转,知道根节点的位置;使用基于递归插入操作和旋转 操作的策略可以使得最近插入的元素接近BST树的顶部,同时保持树的平衡性。这种插入方式称为从根部插入,实现策略:首先使用普通递归插入将在树底部找到 一个合适的位置插入新的节点,然后使用旋转操作将这个新加入的节点旋转到根节点处,不仅可以保持树的平衡,而且由于最近插入的项被使用的概率大,靠近根节 点则加速搜索效率;

  • 旋转操作:BST树中从根部插入新节点:首要考虑的就是是否能够保持BST树的性质。现在使用基于旋转(Rotation)的转换策略,使得BST树保持原有性质。旋转实质上是交换根节点和一个孩子的角色,同时保持各节点的顺序

  • 选择第Kth个值(最小或者最大):利用Node节点中的count标记(此标记说明以当前节点为根节点的子树的所有节点数),可以快速查找给定的序列中 第Kth个最小或者最大值;当然前提是将给定的序列扩建成BST;从根节点开始,首先检查其左子树中节点个数,如果正好为K个则返回根节点本身,如果大于 K个节点,则对左子树递归调用算法,如果小于K个节点,则说明第K个最小键在根节点的右子树中,变成查找右子树中第K-t-1个最小键的项(t为左子树所 有节点,1为根节点自身);

  • BST树的节点删除操作:被删除的节点可以有三种情况,没有子节点,有一个子节点,有两个子节点。第一种情况可直接删除;第二种情况需要临时存储子节点的 索引,并让被删除节点的父节点指向这个这个索引;第三种情况需要维护BST树的性质,所以一般性策略是选择右子树中最小的元素作为新的根节点(右子树中最 小的元素出现在最左边,所以它至多只有一个子节点,可容易删除),然而有时候也会选择左子树中的最大元素作为新的根节点(由于在左右子树中任意选择新的节 点作为新的根节点,所以可能造成BST树的不平衡);

样例:

  1 struct Node {
  2         int value;
  3         int count;
  4         Node *left;
  5         Node *right;
  6         Node(int v, int c=1, Node* l=NULL, Node* r=NULL):
  7                                 count(c), value(v), left(l), right(r) {
  8 
  9         }
 10 };
 11 /**
 12  * 对root节点进行右旋转操作,也就是:
 13  * 1. 让root原来的左孩子变成newRoot;
 14  * 2. 让root变成newRoot的右子节点;
 15  * 3. 让root原来的左孩子的的右子节点变成root的左子节点
 16  * */
 17 Node* rightRotate(Node *root) {
 18         Node *newRoot=root->left;
 19         root->left=root->left->right;
 20         newRoot->right=root;
 21         return newRoot;
 22 }
 23 /**
 24  * 对root节点进行左旋转操作,也就是:
 25  * 1. 让root原来的右孩子变成newRoot;
 26  * 2. 让root变成newRoot的左子节点;
 27  * 3. 让root原来的右孩子的左子节点变成root的右子节点
 28  * */
 29 Node* leftRotate(Node *root) {
 30         Node *newRoot=root->right;
 31         root->right=root->right->left;
 32         newRoot->left=root;
 33         return newRoot;
 34 }
 35 
 36 Node* binaryTreeSearch(Node *root, int target) {
 37 
 38         if(root==NULL)
 39                 return NULL;
 40 
 41         if(target>root->value)
 42                 return binaryTreeSearch(root->right, target);
 43         else if(target<root->value)
 44                 return binaryTreeSearch(root->left, target);
 45         else
 46                 return root;
 47 }
 48 
 49 Node* binaryTreeInsert(Node *root, int target) {
 50 
 51         if(root==NULL) {
 52                 return new Node(target);
 53         }
 54 
 55         if(target>root->value)
 56                 root->right=binaryTreeInsert(root->right, target);
 57         else if(target<root->value)
 58                 root->left=binaryTreeInsert(root->left, target);
 59 
 60         return root;
 61 }
 62 /**
 63  * 这样可以将新插入的元素旋转到为root;
 64  * 不仅可以保持BST的平衡性,而且可以保证
 65  * 新插入的元素的最大访问延迟;
 66  * */
 67 Node* binaryTreeInsertTopDown(Node *root, int target) {
 68 
 69         if(root==NULL) {
 70                 return new Node(target);
 71         }
 72 
 73         if(target>root->value) {
 74                 root->right=binaryTreeInsert(root->right, target);
 75                 root=leftRotate(root);
 76         }
 77         else if(target<root->value) {
 78                 root->left=binaryTreeInsert(root->left, target);
 79                 root=rightRotate(root);
 80         }
 81 
 82         return root;
 83 }
 84 
 85 Node* binaryTreeInsertWithCount(Node *root, int target) {
 86 
 87         if(root==NULL) {
 88                 return new Node(target);
 89         }
 90 
 91         if(target>root->value)
 92                 root->right=binaryTreeInsert(root->right, target);
 93         else if(target<root->value)
 94                 root->left=binaryTreeInsert(root->left, target);
 95         root->count++;
 96         return root;
 97 }
 98 /**
 99  * 从一个序列中选定第K大的数字,
100  * */
101 int binaryTreeSelect(Node *root, int k) {
102         /**
103          * 如果当前root为NULL,则选择失败
104          * */
105         if(root==NULL) {
106                 printf("\nfind nothing-_-\n");
107                 return -1;
108         }
109         /**
110          * 如果root的左子节点为NULL
111          * */
112         if(root->left==NULL) {
113                 if(k==1)
114                         return root->value;
115                 return binaryTreeSelect(root->right, k-1);
116         }
117         /**
118          * 如果root的左子节点不为NULL;
119          * 1. 如果K<=leftCount,则Kth个节点在左子树中
120          * 2. 如果K==leftCount+1,则kth个节点就是root自身
121          * 3. 如果k>leftCount+1,则Kth个节点就是右子树中的k-1-leftCount个节点
122          * */
123         int leftCount=root->left->count;
124         if(leftCount>=k)
125                 return binaryTreeSelect(root->left, k);
126         else if(leftCount+1==k)
127                 return root->value;
128         else
129                 return binaryTreeSelect(root->right, k-1-leftCount);
130 }
131 
132 /**
133  * 将指定的元素target旋转到根节点
134  * */
135 Node* binaryTreeRotate(Node *root, int target) {
136 
137         if(root==NULL)
138                 return NULL;
139 
140         if(target>root->value) {
141                 root->right=binaryTreeRotate(root->right,target);
142                 leftRotate(root);
143         } else if(target<root->value) {
144                 root->left=binaryTreeRotate(root->left,target);
145                 rightRotate(root);
146         }
147 
148         return root;
149 }
150 /**
151  * 此方法寻找root的左子树中具有最大value的子节点,也就是最‘左边’的子节点;
152  * */
153 Node* subtreeRightMaximum(Node *root) {
154         Node *cur=root;
155         Node *pre;
156         while(cur!=NULL) {
157                 pre=cur;
158                 cur=cur->left;
159         }
160         return pre;
161 }
162 /**
163  * 此方法寻找root的右子树中具有最大value的子节点,也就是最‘左边’的子节点;
164  * */
165 Node* subTreeLeftMaximum(Node* root) {
166         Node *cur=root;
167         Node *pre;
168         while(cur!=NULL) {
169                 pre=cur;
170                 cur=cur->right;
171         }
172         return pre;
173 }
174 
175 Node* binaryTreeDelete(Node *root, int target) {
176 
177         if(root==NULL)
178                 return NULL;
179         Node *temp;
180         Node *newRoot;
181         /**
182          * 如果target比root->value大,则说明其位于root的
183          * 右子树,则继续递归
184          * 如果target比root->value小,则说明其位于root的
185          * 左子树,则继续递归
186          * 如果target等于root->value,则说明当前节点root
187          * 就是需要删除的节点,然后分三种情况讨论:
188          * 1. 如果root没有左右子节点
189          * 2. 如果root只有左节点或者只有右节点
190          * 3. 如果root德尔左右子节点都存在;
191          * */
192         if(target>root->value)
193                 root->right=binaryTreeDelete(root->right, target);
194         else if(target<root->value)
195                 root->left=binaryTreeDelete(root->left, target);
196         else {
197                 if(root->left==NULL && root->right) {
198                         delete root;
199                         return NULL;
200                 } else if(root->left==NULL) {
201                         temp=root->right;
202                         delete root;
203                         return temp;
204                 } else if(root->right==NULL) {
205                         temp=root->left;
206                         delete root;
207                         return temp;
208                 }
209                 /**
210                  * 左右子节点都存在的情况,需要从左右子树中寻找下一个根节点;
211                  * 这里是从右子树中选取最小的一个节点作为新的根节点;
212                  * */
213                 newRoot=subtreeRightMaximum(root->right);
214                 /**
215                  * 由于右子树中最小的节点必然至多只有一个右节点,所以其删除操作
216                  * 较为简单;然后将其的左右子树替换成当前的左右子树;
217                  * */
218                 newRoot=binaryTreeDelete(root->right, newRoot->value);
219                 newRoot->right=root->right;
220                 newRoot->left=root->left;
221                 delete root;
222         }
223 
224 }

补充:

  • BST中搜索和插入的策略都是一样的,从传入的树节点开始,首先判断其是否为NULL,如果是的话对于搜索来讲表示失败,对于插入来讲表示需要插入新的节 点;如果不是NULL的话,对于搜索来讲比对是否为目标值,然后针对左右子树递归调用,对于插入来讲比对是否相同,表示树中已经有同样的节点算法说明;

  • BST树的构建和搜索也使用同样的遍历策略,所以插入与搜索一样容易实现;旋转可用于防止树变得不平衡,实现删除,合并和其他操作的辅助操作,BST树的 插入操作可以通过在树的底部插入新元素,然后使用左旋和右旋将新元素带到根节点处,防止树的不平衡状态。每次BST搜索命中的项也可以通过旋转带到根节点 处;

  • 使用BST树进行选择算法最大的缺点就是计数域的出现导致额外的内存占用,树结构改变时需要额外的维护操作,同时我们可以对查找到的节点元素使用旋转操作,将其放到根节点的位置,下次使用的时候就能很快定位;


BST树的性能特征总结:

  • 二叉搜索树算法的运行时间取决于树的形状,最好情况下树可能完全平衡,这样一次搜索过程就是一条路径的长度㏒N,最差情况下树退化为链表,这样一次搜索过程路径长度可能为N;

  • 使用插入操作构建BST树的过程中,越是前面的节点对树最终形状的影响越是大,第一个元素就是树根,对于随机序列来讲,最坏情况出现的概率很小,所以平均情况能保持较好的运行时间,㏒N;

  • 使用索引项来表示搜索节点,避免动态分配内存。当序列以随机序列插入时,生成完全平衡树的概率很小,但二叉树路径的长度和树的高度与BST的搜索开销联系 紧密。平均情况下一棵根据N个随机键生成的BST树中,搜索命中(插入和搜索失败)大约需要1.39㏒N次比较。最坏情况下,可能需要N此比较(也就是顺 序搜索);

转载于:https://www.cnblogs.com/leo-chen-2014/p/3762132.html

相关文章:

定义自定义的异常

首先我们建立自己的异常类CustomException&#xff0c;它要继承自ApplicationException类&#xff08;这是一个在发生非致命的应用程序错误时抛出的通用异常&#xff0c;它又继承于更为通用的Exception类&#xff09;&#xff0c;将其用作为应用程序定义的任何自定义异常的基类…

python3 的 round 函数的 练习

python3 的 round 函数感觉很别扭&#xff0c;其运算结果与习惯不相符。特记录下来&#xff1a; 代码 python 3的 round 函数 是“四舍六入五成双”的https://www.zhihu.com/question/20128906print(python 3的 round 函数&#xff1a;四舍六入五成双)print(\nround(-3.5) , …

Visual Studio UML Activity Diagram(2)

昨天的图文介绍了Visual Studio UML Activity Diagram中所涉及的对象&#xff0c;今天图文我们来介绍这些对象的属性部分并给出UML关于Activity Diagram的元模型类图。通常情况下&#xff0c;我们在做一套软件系统的时候&#xff0c;对甲方业务流程并不熟悉&#xff0c;如果直接…

Go 语言中手动内存管理

2019独角兽企业重金招聘Python工程师标准>>> Go 语言是自带GC的, 相对C语言等的手动内存管理省事很多, 弊端便是会消耗更多的内存, 以及在GC时导致整个程序的停顿. 在某些特殊场合, 如果能够可选地手动进行内存管理, 效果会好不少. Go 目前的 GC 实现比较简单(mark-…

依赖倒转原则(Dependency Inversion Principle,DIP)

前面两篇图文介绍了“开闭原则”和“里氏替换原则”。开发出对扩展开放&#xff0c;对修改封闭的系统是程序员的目标&#xff0c;而今天所介绍的“依赖倒转原则”正是实现这一目标的途径之一&#xff0c;而“里氏替换原则”为这一途径提供了保证。大家或许发现&#xff0c;我写…

细说浏览器特性检测(2)-通用事件检测

在上一篇中介绍了jQuery1.4版本新增的几个浏览器特性检测方案和具体的目的&#xff0c;本文将以事件为中心&#xff0c;介绍一个较为完整、通用的事件检测方案。 事件检测&#xff0c;即检测某一事件在不同的浏览器中是否存在&#xff08;可用&#xff09;&#xff0c;这在编写…

robot简单功能测试脚本设计(例子)

以学生管理系统的添加一个学生信息为例子页面对象&#xff1a;editbox&#xff08;姓名&#xff09;,button&#xff08;添加&#xff09;数据要求&#xff1a;1 姓名不能为空2 姓名不能重复程序结构1 点button&#xff0c;弹出对话框“姓名不能为空”2 输入姓名&#xff0c;点…

里氏替换原则(Liskov Substitution Principle,LSP)

昨天图文介绍了软件设计的一个基本原则“开闭原则”&#xff0c;而“开闭原则”的核心就是通过抽象把需求变化进行隔离&#xff0c;这种想法可以通过“里氏替换原则”进行保证。理解“里氏替换原则”也是理解面向对象中“运行时多态”的关键。希望大家仔细体会。

在IIS7里配置 ISAPI,运行dll程序,总提示下载dll

在IIS7里配置 ISAPI&#xff0c;运行dll程序&#xff0c;总提示下载dll&#xff0c;只需要把对应站点应用程序池里面的高级设置里的启用32位应用程序&#xff0c;设为“true"即可。

MySQL数据库高可用集群搭建-PXC集群部署

Percona XtraDB Cluster&#xff08;下文简称PXC集群&#xff09;提供了MySQL高可用的一种实现方法。集群是有节点组成的&#xff0c;推荐配置至少3个节点&#xff0c;但是也可以运行在2个节点上。 PXC原理描述&#xff1a; 分布式系统的CAP理论&#xff1a; C&#xff1a;一致…

搭建Jupyter学习环境

python notebook是一个基于浏览器的python数据分析工具&#xff0c;使用起来非常方便&#xff0c;具有极强的交互方式和富文本的展示效果。jupyter是它的升级版&#xff0c;它的安装也非常方便&#xff0c;一般Anaconda安装包中会自带。安装好以后直接输入jupyter notebook便可…

[转贴]2006十大经典语句

1. 骑白马的不一定是王子&#xff0c;他可能是唐僧&#xff1b; 2. 带翅膀的也不一定是天使&#xff0c;他可能是鸟人。 3. 站的更高&#xff0c;尿的更远。 4. 穿别人的鞋&#xff0c;走自己的路&#xff0c;让他们找去吧&#xff0c; 5. 我不是随便的人。我随便起来不是人 6.…

开放-封闭原则(The Open-Closed Principle,OCP)

自己设计的软件系统“易于维护”、“扩展性好”、“可重用”、“具有灵活性”&#xff0c;这是每位程序员所追求的目标。“开闭原则”为我们指明了方向&#xff0c;即我们所设计的软件尽量满足“开闭原则–对扩展开放&#xff0c;对修改关闭”&#xff0c;这样就能降低需求不断…

Interesting visualization tools for profiling.

Interesting visualization tools for profiling. http://dtrace.org/blogs/brendan/2012/03/17/linux-kernel-performance-flame-graphs/ http://dtrace.org/blogs/brendan/2013/07/01/detecting-outliers/转载于:https://www.cnblogs.com/kungfupanda/p/3245651.html

javascript网页开发 第二章

HTML高级部分 2.1. 表格标签 2.1.1 <table></table> Bgcolor 设置表格的背景色 Border 设置边框的宽度 Bordercolor 设置边框的颜色 Bordercolorlight 设置边框明亮部分的颜色 Bordercolordark 设置边框昏暗部分的颜色 Cellspacing 设置单元格之间的间隔大小 Cel…

ORACLE JET BASIC TABLE

转载于:https://blog.51cto.com/feitai/1917581

Visual Studio UML Use Case Diagram(1)

前几天我们介绍了Visual Studio UML Activity Diagram&#xff0c;今天我们介绍Visual Studio UML Use Case Diagram的内容。通常RUP按照动态划分&#xff0c;分为周期、阶段、里程碑、迭代&#xff0c;按照静态划分&#xff0c;分为角色、制品、工作流、活动&#xff0c;在Wor…

可以左右移动多选下拉列表的javaScipt(可以兼容IE和firefox)

自己在项目业余时间总结了一份可以左右移动&#xff08;Add和remove&#xff09;多选下拉列表的javaScipt,可以兼容IE和firefox,并且经过测试&#xff0c;只是代码略显臃肿&#xff0c;希望各位网友参考后给一些指点&#xff0c;特别是在简化代码方面。我在让其兼容 firefox很是…

OLAP和OLTP的区别(基础知识)

联机分析处理 (OLAP) 的概念最早是由关系数据库之父E.F.Codd于1993年提出的&#xff0c;他同时提出了关于OLAP的12条准则。OLAP的提出引起了很大的反响&#xff0c;OLAP作为一类产品同联机事务处理 (OLTP) 明显区分开来。当今的数据处理大致可以分成两大类&#xff1a;联机事务…

如何让phpmyadmin输入密码再进入

分类&#xff1a;wamp对于很多不熟悉PHP环境安装的朋友来说&#xff0c;用集成环境可以更快的上手&#xff0c;更方便的搭建PHP的运行环境&#xff0c;但是&#xff0c;WAMP的集成环境仅仅是将底层基础工作做好了&#xff0c;有些个别关键的配置操作并没有集成到环境安装中&…

Visual Studio UML Use Case Diagram(2)

Use Case Model是捕获用户需求确定系统边界最流行的方法。Use Case Model由两部分组成Use Case Diagram和Use Case Specification&#xff0c;对于不方便描述的部分可以放在Supplementary Specification中&#xff0c;通过Glossary统一大家的用词规范。昨天我们介绍了Visual St…

Delphi下利用WinIo模拟鼠标键盘详解

本文最早在编程论坛上发表&#xff0c;文章地址&#xff1a;http://programbbs.com/bbs/view12-17207-1.htm&#xff0c;相关文件可以在上述地址的页面中下载。转载时请注明出处。 前言 一日发现SendInput对某程序居然无效&#xff0c;无奈只好开始研究WinIo。上网查了很多资料…

在vs2005中使用Jmail发送邮件问题

jmail.Message Jmail new jmail.Message(); DateTime t DateTime.Now; String Subject " From EMail .net"; String body "你好科学12:15"; String FromEmail "jsyxo163.com"; String ToEmail…

nginx学习之静态内容篇(五)

1.根目录和索引文件 server {root /www/data;location / {}location /images/ {}location ~ \.(mp3|mp4) {root /www/media;} } root指令能放置的位置是&#xff1a;http&#xff0c;server&#xff0c;location。 上面的意思是&#xff1a;我所有的location定义都是基于根目录…

Modeling System Behavior with Use Case(1)

Modeling System Behavior with Use case 我们分为三个部分进行介绍&#xff0c;主要内容包括&#xff1a;需求简介、Use Case Model&#xff08;Use Case Diagram、Use Case Specification&#xff09;、Supplimentary Specification和Glossary&#xff0c;这部分内容是开发过…

matlab练习程序(高斯牛顿法最优化)

计算步骤如下&#xff1a; 图片来自《视觉slam十四讲》6.2.2节。 下面使用书中的练习yexp(a*x^2b*xc)w这个模型验证一下&#xff0c;其中w为噪声&#xff0c;a、b、c为待解算系数。 代码如下&#xff1a; clear all; close all; clc;a1;b2;c1; %待求解的系数x(0:0…

和Office一起做减肥操

随着微软公司的不断开发&#xff0c;Microsoft Office这款大家熟悉的软件真是越来越好用。可是随着版本的更新&#xff0c;软件的身材却越来越“肥胖”&#xff0c;于是很多朋友总想知道如何给它们“减肥”&#xff1f;今天&#xff0c;我们就说一说如何为Office2003减肥吧&…

codevs——1220 数字三角形(棋盘DP)

时间限制: 1 s空间限制: 128000 KB题目等级 : 黄金 Gold题解题目描述 Description如图所示的数字三角形&#xff0c;从顶部出发&#xff0c;在每一结点可以选择向左走或得向右走&#xff0c;一直走到底层&#xff0c;要求找出一条路径&#xff0c;使路径上的值最大。 输入描述 …

Modeling System Behavior with Use Case(2)

这是Modeling System Behavior with Use Case的第二部分&#xff0c;本图文首先介绍Use Case Model&#xff0c;然后介绍Actor以及Actor之间的关系&#xff0c;Use Case以及Use Case之间的关系&#xff0c;最后介绍Actor与Use Case之间的关系。

【Python】keras卷积神经网络识别mnist

卷积神经网络的结构我随意设了一个。 结构大概是下面这个样子&#xff1a; 代码如下&#xff1a; import numpy as np from keras.preprocessing import image from keras.models import Sequential from keras.layers import Dense, Dropout, Flatten, Activation from keras.…