当所有的静态查找结构添加和删除一个数据的时候,整个结构都需要重建。这对于常常需要在查找过程中动态改变数据而言,是灾难性的。因此人们就必须去寻找高效的动态查找结构,我们在这讨论一个非常常用的动态查找树——二叉查找树 

二叉查找树的特点

下面的图就是两棵二叉查找树,我们可以总结一下他的特点:

(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)))。

这说明:同样一组数据集合,不同的添加顺序会导致查找树的结构完全不一样,直接影响了查找效率。

那么如何解决这个问题呢? 我们会在下面的专题中:《平衡二叉树》 中来解决。

  1. package net.hr.algorithm.search;
  2.  
  3. import java.util.ArrayList;
  4.  
  5. /**
  6. * 二叉树节点结构
  7. * @author heartraid
  8. */
  9. class BSTNode<E extends Comparable<E>>{
  10. /**结点关键字*/
  11. E key=null;
  12. /**直接父亲结点*/
  13. BSTNode<E> parent=null;
  14. /**结点左子树的根节点*/
  15. BSTNode<E> lchild=null;
  16. /**结点右子树的根节点*/
  17. BSTNode<E> rchild=null;
  18.  
  19. BSTNode(E k){
  20. this.key=k;
  21. }
  22.  
  23. }
  24. /**
  25. * 二叉查找树 Binary Search Tree(BST)
  26. * @author heartraid
  27. *
  28. */
  29. public class BST<E extends Comparable<E>> {
  30. /**树根*/
  31. private BSTNode<E> root=null;
  32.  
  33. public BST(){
  34. }
  35.  
  36. /**
  37. * BST 查询关键字
  38. * @param key 关键字
  39. * @return 查询成功/true, 查询失败/false
  40. */
  41. public boolean search(E key){
  42. System.out.print("搜索关键字["+key+"]:");
  43. if(key==null||root==null){
  44. System.out.println("搜索失败");
  45. return false;
  46. }
  47. else{
  48. System.out.print("搜索路径[");
  49. if(searchBST(root,key)==null){
  50. return false;
  51. }
  52. else return true;
  53.  
  54. }
  55. }
  56. /**
  57. * BST插入关键字
  58. * @param key 关键字
  59. * @return 插入成功/true, 插入失败/false
  60. */
  61. public boolean insert(E key){
  62. System.out.print("插入关键字["+key+"]:");
  63. if(key==null) return false;
  64. if(root==null){
  65. System.out.println("插入到树根。");
  66. root=new BSTNode<E>(key);
  67. return true;
  68. }
  69. else{
  70. System.out.print("搜索路径[");
  71. return insertBST(root,key);
  72. }
  73. }
  74.  
  75. public boolean delete(E key){
  76. System.out.print("删除关键字["+key+"]:");
  77. if(key==null||root==null){
  78. System.out.println("删除失败");
  79. return false;
  80. }
  81. else{
  82. System.out.print("搜索路径[");
  83.  
  84. //定位到树中待删除的结点
  85. BSTNode<E> nodeDel=searchBST(root,key);
  86. if(nodeDel==null){
  87. return false;
  88. }
  89. else{
  90. //nodeDel的右子树为空,则只需要重接它的左子树
  91. if(nodeDel.rchild==null){
  92.  
  93. BSTNode<E> parent=nodeDel.parent;
  94. if(parent.lchild.key.compareTo(nodeDel.key)==0)
  95. parent.lchild=nodeDel.lchild;
  96. else
  97. parent.rchild=nodeDel.lchild;
  98. }
  99. //左子树为空,则重接它的右子树
  100. else if(nodeDel.lchild==null){
  101. BSTNode<E> parent=nodeDel.parent;
  102. if(parent.lchild.key.compareTo(nodeDel.key)==0)
  103. parent.lchild=nodeDel.rchild;
  104. else
  105. parent.rchild=nodeDel.rchild;
  106. }
  107. //左右子树均不空
  108. else{
  109. BSTNode<E> q=nodeDel;
  110. //先找nodeDel的左结点s
  111. BSTNode<E> s=nodeDel.lchild;
  112. //然后再向s的右尽头定位(这个结点将替代nodeDel),其中q一直定位在s的直接父亲结点
  113. while(s.rchild!=null){
  114. q=s;
  115. s=s.rchild;
  116. }
  117. //换掉nodeDel的关键字为s的关键字
  118. nodeDel.key=s.key;
  119. //重新设置s的左子树
  120. if(q!=nodeDel)
  121. q.rchild=s.lchild;
  122. else
  123. q.lchild=s.lchild;
  124. }
  125. return true;
  126. }
  127. }
  128. }
  129.  
  130. /**
  131. * 递归查找关键子
  132. * @param node 树结点
  133. * @param key 关键字
  134. * @return 查找成功,返回该结点,否则返回null。
  135. */
  136. private BSTNode<E> searchBST(BSTNode<E> node, E key){
  137. if(node==null){
  138. System.out.println("]. 搜索失败");
  139. return null;
  140. }
  141. System.out.print(node.key+" —>");
  142. //搜索到关键字
  143. if(node.key.compareTo(key)==0){
  144. System.out.println("]. 搜索成功");
  145. return node;
  146. }
  147. //在左子树搜索
  148. else if(node.key.compareTo(key)>0){
  149. return searchBST(node.lchild,key);
  150. }
  151. //在右子树搜索
  152. else{
  153. return searchBST(node.rchild,key);
  154. }
  155. }
  156.  
  157. /**
  158. * 递归插入关键字
  159. * @param node 树结点
  160. * @param key 树关键字
  161. * @return true/插入成功,false/插入失败
  162. */
  163. private boolean insertBST(BSTNode<E> node, E key){
  164. System.out.print(node.key+" —>");
  165. //在原树中找到相同的关键字,无需插入。
  166. if(node.key.compareTo(key)==0)
  167. {
  168. System.out.println("]. 搜索有相同关键字,插入失败");
  169. return false;
  170. }
  171. else{
  172. //搜索node的左子树
  173. if(node.key.compareTo(key)>0){
  174. //如果当前node的左子树为空,则将新结点key node插入到左孩子处
  175. if(node.lchild==null) {
  176. System.out.println("]. 插入到"+node.key+"的左孩子");
  177. BSTNode<E> newNode=new BSTNode<E>(key);
  178. node.lchild=newNode;
  179. newNode.parent=node;
  180. return true;
  181. }
  182. //如果当前node的左子树存在,则继续递归左子树
  183. else return insertBST(node.lchild, key);
  184. }
  185. //搜索node的右子树
  186. else{
  187. if(node.rchild==null){
  188. System.out.println("]. 插入到"+node.key+"的右孩子");
  189. BSTNode<E> newNode=new BSTNode<E>(key);
  190. node.rchild=newNode;
  191. newNode.parent=node;
  192. return true;
  193. }
  194. else return insertBST(node.rchild,key);
  195. }
  196. }
  197.  
  198. }
  199. /**
  200. * 得到BST根节点
  201. * @return BST根节点f
  202. */
  203. public BSTNode<E> getRoot(){
  204. return this.root;
  205. }
  206. /**
  207. * 非递归中序遍历BST
  208. */
  209. public void InOrderTraverse(){
  210. if(root==null)
  211. return;
  212. BSTNode<E> node=root;
  213. ArrayList<BSTNode<E>> stack=new ArrayList<BSTNode<E>>();
  214. stack.add(node);
  215. while(!stack.isEmpty()){
  216. while(node.lchild!=null){
  217. node=node.lchild;
  218. stack.add(node);
  219. }
  220. if(!stack.isEmpty()){
  221. BSTNode<E> topNode=stack.get(stack.size()-1);
  222. System.out.print(topNode.key+" ");
  223. stack.remove(stack.size()-1);
  224. if(topNode.rchild!=null){
  225. node=topNode.rchild;
  226. stack.add(node);
  227. }
  228. }
  229. }
  230.  
  231. }
  232.  
  233. /**
  234. * 测试
  235. */
  236. public static void main(String[] args) {
  237. BST<Integer> tree=new BST<Integer>();
  238. tree.insert(new Integer(100));
  239. tree.insert(new Integer(52));
  240. tree.insert(new Integer(166));
  241. tree.insert(new Integer(74));
  242. tree.insert(new Integer(11));
  243. tree.insert(new Integer(13));
  244. tree.insert(new Integer(66));
  245. tree.insert(new Integer(121));
  246.  
  247. tree.search(new Integer(11));
  248. tree.InOrderTraverse();
  249.  
  250. tree.delete(new Integer(11));
  251. tree.InOrderTraverse();
  252.  
  253. }
  254.  
  255. }

【查找结构 2】二叉查找树 [BST]的更多相关文章

  1. 【查找结构3】平衡二叉查找树 [AVL]

    在上一个专题中,我们在谈论二叉查找树的效率的时候.不同结构的二叉查找树,查找效率有很大的不同(单支树结构的查找效率退化成了顺序查找).如何解决这个问题呢?关键在于如何最大限度的减小树的深度.正是基于这 ...

  2. 查找系列合集-二叉查找树BST

    一. 二叉树 1. 什么是二叉树? 在计算机科学中,二叉树是每个结点最多有两个子树的树结构. 通常子树被称作“左子树”(left subtree)和“右子树”(right subtree). 二叉树常 ...

  3. [学习笔记] 二叉查找树/BST

    平衡树前传之BST 二叉查找树(\(BST\)),是一个类似于堆的数据结构, 并且,它也是平衡树的基础. 因此,让我们来了解一下二叉查找树吧. (其实本篇是作为放在平衡树前的前置知识的,但为了避免重复 ...

  4. 二叉查找树(BST)

    二叉查找树(BST):使用中序遍历可以得到一个有序的序列

  5. 二叉查找树BST 模板

    二叉查找树BST 就是二叉搜索树 二叉排序树. 就是满足 左儿子<父节点<右儿子 的一颗树,插入和查询复杂度最好情况都是logN的,写起来很简单.   根据BST的性质可以很好的解决这些东 ...

  6. 【查找结构5】多路查找树/B~树/B+树

    在前面专题中讲的BST.AVL.RBT都是典型的二叉查找树结构,其查找的时间复杂度与树高相关.那么降低树高自然对查找效率是有所帮助的.另外还有一个比较实际的问题:就是大量数据存储中,实现查询这样一个实 ...

  7. 二叉查找树(BST)的实现

    一.二叉树介绍 二叉查找树(Binary Search Tree,BST),又称二叉排序树,也称二叉搜索树,它或者是一颗空树,或者具有如下性质的树:若它的左子树不为空,则左子树上所有节点的值都小于根节 ...

  8. 3.2 符号表之二叉查找树BST

    一.插入和查找 1.二叉查找树(Binary Search Tree)是一棵二叉树,并且每个结点都含有一个Comparable的键,保证每个结点的键都大于其左子树中任意结点的键而小于其右子树的任意结点 ...

  9. 【查找结构4】红黑树 [RBT]

    红黑树的性质与定义 红黑树(red-black tree) 是一棵满足下述性质的二叉查找树: 1. 每一个结点要么是红色,要么是黑色. 2. 根结点是黑色的. 3. 所有叶子结点都是黑色的(实际上都是 ...

随机推荐

  1. 字符串比较 忽略大小写 iphone

    //不考虑大小写比较字符串1 NSString *astring01 = @"this is a String!"; NSString *astring02 = @"Th ...

  2. 怎么用PHP发送HTTP请求(转载)

    本文转自:http://blog.snsgou.com/blog/161.html  file_get_contents版本: /** * 发送post请求 * @param string $url ...

  3. Careercup - Microsoft面试题 - 5204967652589568

    2014-05-11 23:57 题目链接 原题: identical balls. one ball measurements ........ dead easy. 题目:9个看起来一样的球,其中 ...

  4. 【LRU Cache】cpp

    题目: Design and implement a data structure for Least Recently Used (LRU) cache. It should support the ...

  5. Hibernate中启用日志

    Problem How do you determine what SQL query is being executed by Hibernate? How can you see the Hibe ...

  6. 【CentOs】开机启动与防火墙

    说明: 开机启动使用的命令式chkconfig .防火墙相关的命令式iptables 1.chkconfig 2.iptables 1.chkconfig 参数: --add   新增所指定的系统服务 ...

  7. 【转载】Web应用工作原理

    问题描述:           Web应用工作原理   问题解决:          参考资料:http://blog.csdn.net/lcore/article/details/8964642   ...

  8. Leetcode#59 Spiral Matrix II

    原题地址 相比于Spiral Matrix(参见这篇文章)要简单一些,因为是方阵,所以代码简洁一些. 注意当n是奇数的时候,中心小块要单独赋值(代码21行) 代码: vector<vector& ...

  9. SQL Server优化

    虽然查询速度慢的原因很多,但是如果通过一定的优化,也可以使查询问题得到一定程度的解决. 查询速度慢的原因很多,常见如下几种: 没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷) I/ ...

  10. depthstencil buffer 不支持 msaa

    phyreengine dx11 MRT不支持 depth rendertarget 的msaa 他里面竟然只写着,// not supported yet !!!! 导致hdao 时开msaa的话, ...