【查找结构 2】二叉查找树 [BST]
当所有的静态查找结构添加和删除一个数据的时候,整个结构都需要重建。这对于常常需要在查找过程中动态改变数据而言,是灾难性的。因此人们就必须去寻找高效的动态查找结构,我们在这讨论一个非常常用的动态查找树——二叉查找树 。
二叉查找树的特点
下面的图就是两棵二叉查找树,我们可以总结一下他的特点:
(1) 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值
(2) 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
(3) 它的左、右子树也分别为二叉查找树
我们中序遍历这两棵树发现一个有序的数据序列: 【1 2 3 4 5 6 7 8 】
二叉查找树的操作
插入操作:
现在我们要查找一个数9,如果不存在则,添加进a图。我们看看二叉查找树动态添加的过程:
1). 数9和根节点4比较(9>4),则9放在节点4的右子树中。
2). 接着,9和节点5比较(9>5),则9放在节点5的右子树中。
3). 依次类推:直到9和节点8比较(9>8),则9放在节点8的右子树中,成为节点8的右孩子。
这个过程我们能够发现,动态添加任何一个数据,都会加在原树结构的叶子节点上,而不会重新建树。 由此可见,动态查找结构确实在这方面有巨大的优势。
删除操作:
如果二叉查找树中需要删除的结点左、右子树都存在,则删除的时候需要改变一些子树结构,但所需要付出的代价很小。
具体的插入,删除算法请参加《数据结构算法与应用——搜索树》P5-8。[该章节已经上传到《查找结构专题(6):动态查找树比较 》中]。
二叉查找树的效率分析
那么我们再来看看二叉查找树的效率问题
很显然,在a,b两图的二叉查找树结构中查找一个数据,并不需要遍历全部的节点元素,查找效率确实提高了。但是有一个很严重的问题:我们在a图中查找8需要比较5次数据,而在B图中只需要比较3次。更为严重的是:如果按有序序列[1 2 3 4 5 6 7 8]建立一颗二叉查找树,整棵树就退化成了一个线性结构(如c输入图:单支树),此时查找8需要比较8次数据,和顺序查找没有什么不同。
总结一下:最坏情况下,构成的二叉排序树蜕变为单支树,树的深度为n,其查找时间复杂度与顺序查找一样O(N)。最好的情况是二叉排序树的形态和折半查找的判定树相同,其平均查找长度和log2(N)成正比 (O(log2(n)))。
这说明:同样一组数据集合,不同的添加顺序会导致查找树的结构完全不一样,直接影响了查找效率。
那么如何解决这个问题呢? 我们会在下面的专题中:《平衡二叉树》 中来解决。
package net.hr.algorithm.search; import java.util.ArrayList; /** * 二叉树节点结构 * @author heartraid */ class BSTNode<E extends Comparable<E>>{ /**结点关键字*/ E key=null; /**直接父亲结点*/ BSTNode<E> parent=null; /**结点左子树的根节点*/ BSTNode<E> lchild=null; /**结点右子树的根节点*/ BSTNode<E> rchild=null; BSTNode(E k){ this.key=k; } } /** * 二叉查找树 Binary Search Tree(BST) * @author heartraid * */ public class BST<E extends Comparable<E>> { /**树根*/ private BSTNode<E> root=null; public BST(){ } /** * BST 查询关键字 * @param key 关键字 * @return 查询成功/true, 查询失败/false */ public boolean search(E key){ System.out.print("搜索关键字["+key+"]:"); if(key==null||root==null){ System.out.println("搜索失败"); return false; } else{ System.out.print("搜索路径["); if(searchBST(root,key)==null){ return false; } else return true; } } /** * BST插入关键字 * @param key 关键字 * @return 插入成功/true, 插入失败/false */ public boolean insert(E key){ System.out.print("插入关键字["+key+"]:"); if(key==null) return false; if(root==null){ System.out.println("插入到树根。"); root=new BSTNode<E>(key); return true; } else{ System.out.print("搜索路径["); return insertBST(root,key); } } public boolean delete(E key){ System.out.print("删除关键字["+key+"]:"); if(key==null||root==null){ System.out.println("删除失败"); return false; } else{ System.out.print("搜索路径["); //定位到树中待删除的结点 BSTNode<E> nodeDel=searchBST(root,key); if(nodeDel==null){ return false; } else{ //nodeDel的右子树为空,则只需要重接它的左子树 if(nodeDel.rchild==null){ BSTNode<E> parent=nodeDel.parent; if(parent.lchild.key.compareTo(nodeDel.key)==0) parent.lchild=nodeDel.lchild; else parent.rchild=nodeDel.lchild; } //左子树为空,则重接它的右子树 else if(nodeDel.lchild==null){ BSTNode<E> parent=nodeDel.parent; if(parent.lchild.key.compareTo(nodeDel.key)==0) parent.lchild=nodeDel.rchild; else parent.rchild=nodeDel.rchild; } //左右子树均不空 else{ BSTNode<E> q=nodeDel; //先找nodeDel的左结点s BSTNode<E> s=nodeDel.lchild; //然后再向s的右尽头定位(这个结点将替代nodeDel),其中q一直定位在s的直接父亲结点 while(s.rchild!=null){ q=s; s=s.rchild; } //换掉nodeDel的关键字为s的关键字 nodeDel.key=s.key; //重新设置s的左子树 if(q!=nodeDel) q.rchild=s.lchild; else q.lchild=s.lchild; } return true; } } } /** * 递归查找关键子 * @param node 树结点 * @param key 关键字 * @return 查找成功,返回该结点,否则返回null。 */ private BSTNode<E> searchBST(BSTNode<E> node, E key){ if(node==null){ System.out.println("]. 搜索失败"); return null; } System.out.print(node.key+" —>"); //搜索到关键字 if(node.key.compareTo(key)==0){ System.out.println("]. 搜索成功"); return node; } //在左子树搜索 else if(node.key.compareTo(key)>0){ return searchBST(node.lchild,key); } //在右子树搜索 else{ return searchBST(node.rchild,key); } } /** * 递归插入关键字 * @param node 树结点 * @param key 树关键字 * @return true/插入成功,false/插入失败 */ private boolean insertBST(BSTNode<E> node, E key){ System.out.print(node.key+" —>"); //在原树中找到相同的关键字,无需插入。 if(node.key.compareTo(key)==0) { System.out.println("]. 搜索有相同关键字,插入失败"); return false; } else{ //搜索node的左子树 if(node.key.compareTo(key)>0){ //如果当前node的左子树为空,则将新结点key node插入到左孩子处 if(node.lchild==null) { System.out.println("]. 插入到"+node.key+"的左孩子"); BSTNode<E> newNode=new BSTNode<E>(key); node.lchild=newNode; newNode.parent=node; return true; } //如果当前node的左子树存在,则继续递归左子树 else return insertBST(node.lchild, key); } //搜索node的右子树 else{ if(node.rchild==null){ System.out.println("]. 插入到"+node.key+"的右孩子"); BSTNode<E> newNode=new BSTNode<E>(key); node.rchild=newNode; newNode.parent=node; return true; } else return insertBST(node.rchild,key); } } } /** * 得到BST根节点 * @return BST根节点f */ public BSTNode<E> getRoot(){ return this.root; } /** * 非递归中序遍历BST */ public void InOrderTraverse(){ if(root==null) return; BSTNode<E> node=root; ArrayList<BSTNode<E>> stack=new ArrayList<BSTNode<E>>(); stack.add(node); while(!stack.isEmpty()){ while(node.lchild!=null){ node=node.lchild; stack.add(node); } if(!stack.isEmpty()){ BSTNode<E> topNode=stack.get(stack.size()-1); System.out.print(topNode.key+" "); stack.remove(stack.size()-1); if(topNode.rchild!=null){ node=topNode.rchild; stack.add(node); } } } } /** * 测试 */ public static void main(String[] args) { BST<Integer> tree=new BST<Integer>(); tree.insert(new Integer(100)); tree.insert(new Integer(52)); tree.insert(new Integer(166)); tree.insert(new Integer(74)); tree.insert(new Integer(11)); tree.insert(new Integer(13)); tree.insert(new Integer(66)); tree.insert(new Integer(121)); tree.search(new Integer(11)); tree.InOrderTraverse(); tree.delete(new Integer(11)); tree.InOrderTraverse(); } }
【查找结构 2】二叉查找树 [BST]的更多相关文章
- 【查找结构3】平衡二叉查找树 [AVL]
在上一个专题中,我们在谈论二叉查找树的效率的时候.不同结构的二叉查找树,查找效率有很大的不同(单支树结构的查找效率退化成了顺序查找).如何解决这个问题呢?关键在于如何最大限度的减小树的深度.正是基于这 ...
- 查找系列合集-二叉查找树BST
一. 二叉树 1. 什么是二叉树? 在计算机科学中,二叉树是每个结点最多有两个子树的树结构. 通常子树被称作“左子树”(left subtree)和“右子树”(right subtree). 二叉树常 ...
- [学习笔记] 二叉查找树/BST
平衡树前传之BST 二叉查找树(\(BST\)),是一个类似于堆的数据结构, 并且,它也是平衡树的基础. 因此,让我们来了解一下二叉查找树吧. (其实本篇是作为放在平衡树前的前置知识的,但为了避免重复 ...
- 二叉查找树(BST)
二叉查找树(BST):使用中序遍历可以得到一个有序的序列
- 二叉查找树BST 模板
二叉查找树BST 就是二叉搜索树 二叉排序树. 就是满足 左儿子<父节点<右儿子 的一颗树,插入和查询复杂度最好情况都是logN的,写起来很简单. 根据BST的性质可以很好的解决这些东 ...
- 【查找结构5】多路查找树/B~树/B+树
在前面专题中讲的BST.AVL.RBT都是典型的二叉查找树结构,其查找的时间复杂度与树高相关.那么降低树高自然对查找效率是有所帮助的.另外还有一个比较实际的问题:就是大量数据存储中,实现查询这样一个实 ...
- 二叉查找树(BST)的实现
一.二叉树介绍 二叉查找树(Binary Search Tree,BST),又称二叉排序树,也称二叉搜索树,它或者是一颗空树,或者具有如下性质的树:若它的左子树不为空,则左子树上所有节点的值都小于根节 ...
- 3.2 符号表之二叉查找树BST
一.插入和查找 1.二叉查找树(Binary Search Tree)是一棵二叉树,并且每个结点都含有一个Comparable的键,保证每个结点的键都大于其左子树中任意结点的键而小于其右子树的任意结点 ...
- 【查找结构4】红黑树 [RBT]
红黑树的性质与定义 红黑树(red-black tree) 是一棵满足下述性质的二叉查找树: 1. 每一个结点要么是红色,要么是黑色. 2. 根结点是黑色的. 3. 所有叶子结点都是黑色的(实际上都是 ...
随机推荐
- Vue2.0的通用组件
饿了么基于Vue2.0的通用组件开发之路(分享会记录) Element:一套通用组件库的开发之路 Element 是由饿了么UED设计.饿了么大前端开发的一套基于 Vue 2.0 的桌面端组件库. ...
- 格式化输出[parts/iomanip]
/* 用ios类中的成员函数来进行IO格式的控制总需要写一条单独的语句,而不能直接嵌入到IO语句中,显得很不方便,因此C++又提供了一种用操作符来控制IO的格式.操作符分为带参和不带参两种,带参的定义 ...
- loadrunner简单使用——HTTP,WebService,Socket压力测试脚本编写
使用loadrunner进行压力测试主要分两步,第一步是编写脚本(比较重点),第二步执行测试(配置都是在界面上点点就行了,当然我只的是比较简单的,能满足日常需要的),第三步分析结果(这一步比较高深,但 ...
- 《JavaScript高级程序设计》第6章 面向对象程序设计
6.1 对象属性 6.1.1 属性类型 1. 数据属性 我们一般所说的属性就是数据属性,它用来将一个字符串名称映射到某个值上 数据属性的4个特性: configurable, enumerable, ...
- Softnet_data
Softnet_data的初始化: 每个CPU的softnet_data是在net_dev_init中初始化的,代码如下: "/net/core/dev.c" "stat ...
- SqlServer with递归查询的使用
1.数据准备假定有一个表DiGui,有两个字段Id int ParentId intId ParentId4 05 07 02 18 515 59 714 1130 1523 1541 18104 2 ...
- 使用Npoi向Excel中插入图片
先把数据库中的数据都导入到Excel表格中,把图片地址的路径全部转成绝对路径. 使用Npoi读取刚导出的Excle表格,把图片那个单元格的图片路径读出来,然后用文件流读取图片,然后通过Npoi把图片放 ...
- 省选加油>_<
今天没有写题诶……看了看以前的模板……明天就要省选了>_<加油~~ 要不再去打局dota吧>_>
- Leetcode#56 Merge Intervals
原题地址 排序+合并,没啥好说的 第一次尝试C++的lambda表达式,有种写js的感觉,很神奇 c11就支持了lambda表达式,仔细想想,我学C++大概就是在09~10年,c11还没有发布,不得不 ...
- 了解javascript中的事件(二)
本文目录如下: 零.寒暄 一.事件的分类 二.事件代理 2.1 问题引出 2.2 什么是事件代理 2.3 完整示例 二.事件代理 三.事件代理思想的用处 四.总结 零.寒暄 这篇博客本该出现在两个月以 ...