http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html

作者: yangecnuyangecnu's Blog on 博客园) 
出处:http://www.cnblogs.com/yangecnu/

英文原文的出处:
http://algs4.cs.princeton.edu/32bst/

前文介绍了符号表的两种实现,无序链表和有序数组,无序链表在插入的时候具有较高的灵活性,而有序数组在查找时具有较高的效率,本文介绍的二叉查找树(Binary Search Tree,BST)这一数据结构综合了以上两种数据结构的优点。

二叉查找树具有很高的灵活性,对其优化可以生成平衡二叉树,红黑树等高效的查找和插入数据结构,后文会一一介绍。

一 定义

二叉查找树(Binary Search Tree),也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树:

1. 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

3. 任意节点的左、右子树也分别为二叉查找树。

4. 没有键值相等的节点(no duplicate nodes)。

如下图,这个是普通的二叉树:

在此基础上,加上节点之间的大小关系,就是二叉查找树:

二 实现

在实现中,我们需要定义一个内部类Node,它包含两个分别指向左右节点的Node,一个用于排序的Key,以及该节点包含的值Value,还有一个记录该节点及所有子节点个数的值Number。

  1. public class BinarySearchTreeSymbolTable<TKey, TValue> : SymbolTables<TKey, TValue> where TKey : IComparable<TKey>, IEquatable<TValue>
  2. {
  3. private Node root;
  4. private class Node
  5. {
  6. public Node Left { get; set; }
  7. public Node Right { get; set; }
  8. public int Number { get; set; }
  9. public TKey Key { get; set; }
  10. public TValue Value { get; set; }
  11.  
  12. public Node(TKey key, TValue value, int number)
  13. {
  14. this.Key = key;
  15. this.Value = value;
  16. this.Number = number;
  17. }
  18. }
  19. ...
  20. }

查找

查找操作和二分查找类似,将key和节点的key比较,如果小于,那么就在Left Node节点查找,如果大于,则在Right Node节点查找,如果相等,直接返回Value。

该方法实现有迭代和递归两种。

递归的方式实现如下:

  1. public override TValue Get(TKey key)
  2. {
  3. TValue result = default(TValue);
  4. Node node = root;
  5. while (node != null)
  6. {
  7.  
  8. if (key.CompareTo(node.Key) > 0)
  9. {
  10. node = node.Right;
  11. }
  12. else if (key.CompareTo(node.Key) < 0)
  13. {
  14. node = node.Left;
  15. }
  16. else
  17. {
  18. result = node.Value;
  19. break;
  20. }
  21. }
  22. return result;
  23. }

迭代的如下:

  1. public TValue Get(TKey key)
  2. {
  3. return GetValue(root, key);
  4. }
  5.  
  6. private TValue GetValue(Node root, TKey key)
  7. {
  8. if (root == null) return default(TValue);
  9. int cmp = key.CompareTo(root.Key);
  10. if (cmp > 0) return GetValue(root.Right, key);
  11. else if (cmp < 0) return GetValue(root.Left, key);
  12. else return root.Value;
  13. }

插入

插入和查找类似,首先查找有没有和key相同的,如果有,更新;如果没有找到,那么创建新的节点。并更新每个节点的Number值,代码实现如下:

  1. public override void Put(TKey key, TValue value)
  2. {
  3. root = Put(root, key, value);
  4. }
  5.  
  6. private Node Put(Node x, TKey key, TValue value)
  7. {
  8. //如果节点为空,则创建新的节点,并返回
  9. //否则比较根据大小判断是左节点还是右节点,然后继续查找左子树还是右子树
  10. //同时更新节点的Number的值
  11. if (x == null) return new Node(key, value, 1);
  12. int cmp = key.CompareTo(x.Key);
  13. if (cmp < 0) x.Left = Put(x.Left, key, value);
  14. else if (cmp > 0) x.Right = Put(x.Right, key, value);
  15. else x.Value = value;
  16. x.Number = Size(x.Left) + Size(x.Right) + 1;
  17. return x;
  18. }
  19.  
  20. private int Size(Node node)
  21. {
  22. if (node == null) return 0;
  23. else return node.Number;
  24. }

插入操作图示如下:

下面是插入动画效果:

随机插入形成树的动画如下,可以看到,插入的时候树还是能够保持近似平衡状态:

最大最小值

如下图可以看出,二叉查找树的最大最小值是有规律的:

从图中可以看出,二叉查找树中,最左和最右节点即为最小值和最大值,所以我们只需迭代调用即可。

  1. public override TKey GetMax()
  2. {
  3. TKey maxItem = default(TKey);
  4. Node s = root;
  5. while (s.Right != null)
  6. {
  7. s = s.Right;
  8. }
  9. maxItem = s.Key;
  10. return maxItem;
  11. }
  12.  
  13. public override TKey GetMin()
  14. {
  15. TKey minItem = default(TKey);
  16. Node s = root;
  17. while (s.Left != null)
  18. {
  19. s = s.Left;
  20. }
  21. minItem = s.Key;
  22. return minItem;
  23. }

以下是递归的版本:

  1. public TKey GetMaxRecursive()
  2. {
  3. return GetMaxRecursive(root);
  4. }
  5.  
  6. private TKey GetMaxRecursive(Node root)
  7. {
  8. if (root.Right == null) return root.Key;
  9. return GetMaxRecursive(root.Right);
  10. }
  11.  
  12. public TKey GetMinRecursive()
  13. {
  14. return GetMinRecursive(root);
  15. }
  16.  
  17. private TKey GetMinRecursive(Node root)
  18. {
  19. if (root.Left == null) return root.Key;
  20. return GetMinRecursive(root.Left);
  21. }

Floor和Ceiling

查找Floor(key)的值就是所有<=key的最大值,相反查找Ceiling的值就是所有>=key的最小值,下图是Floor函数的查找示意图:

以查找Floor为例,我们首先将key和root元素比较,如果key比root的key小,则floor值一定在左子树上;如果比root的key大,则有可能在右子树上,当且仅当其右子树有一个节点的key值要小于等于该key;如果和root的key相等,则floor值就是key。根据以上分析,Floor方法的代码如下,Ceiling方法的代码类似,只需要把符号换一下即可:

  1. public TKey Floor(TKey key)
  2. {
  3. Node x = Floor(root, key);
  4. if (x != null) return x.Key;
  5. else return default(TKey);
  6. }
  7.  
  8. private Node Floor(Node x, TKey key)
  9. {
  10. if (x == null) return null;
  11. int cmp = key.CompareTo(x.Key);
  12. if (cmp == 0) return x;
  13. if (cmp < 0) return Floor(x.Left, key);
  14. else
  15. {
  16. Node right = Floor(x.Right, key);
  17. if (right == null) return x;
  18. else return right;
  19. }
  20. }

删除

删除元素操作在二叉树的操作中应该是比较复杂的。首先来看下比较简单的删除最大最小值得方法。

以删除最小值为例,我们首先找到最小值,及最左边左子树为空的节点,然后返回其右子树作为新的左子树。操作示意图如下:

代码实现如下:

  1. public void DelMin()
  2. {
  3. root = DelMin(root);
  4. }
  5.  
  6. private Node DelMin(Node root)
  7. {
  8. if (root.Left == null) return root.Right;
  9. root.Left = DelMin(root.Left);
  10. root.Number = Size(root.Left) + Size(root.Right) + 1;
  11. return root;
  12. }

删除最大值也是类似。

现在来分析一般情况,假定我们要删除指定key的某一个节点。这个问题的难点在于:删除最大最小值的操作,删除的节点只有1个子节点或者没有子节点,这样比较简单。但是如果删除任意节点,就有可能出现删除的节点有0个,1 个,2个子节点的情况,现在来逐一分析。

当删除的节点没有子节点时,直接将该父节点指向该节点的link设置为null。

当删除的节点只有1个子节点时,将该自己点替换为要删除的节点即可。

当删除的节点有2个子节点时,问题就变复杂了。

假设我们删除的节点t具有两个子节点。因为t具有右子节点,所以我们需要找到其右子节点中的最小节点,替换t节点的位置。这里有四个步骤:

1. 保存带删除的节点到临时变量t

2. 将t的右节点的最小节点min(t.right)保存到临时节点x

3. 将x的右节点设置为deleteMin(t.right),该右节点是删除后,所有比x.key最大的节点。

4. 将x的做节点设置为t的左节点。

整个过程如下图:

对应代码如下:

  1. public void Delete(TKey key)
  2. {
  3. root =Delete(root, key);
  4.  
  5. }
  6.  
  7. private Node Delete(Node x, TKey key)
  8. {
  9. int cmp = key.CompareTo(x.Key);
  10. if (cmp > 0) x.Right = Delete(x.Right, key);
  11. else if (cmp < 0) x.Left = Delete(x.Left, key);
  12. else
  13. {
  14. if (x.Left == null) return x.Right;
  15. else if (x.Right == null) return x.Left;
  16. else
  17. {
  18. Node t = x;
  19. x = GetMinNode(t.Right);
  20. x.Right = DelMin(t.Right);
  21. x.Left = t.Left;
  22. }
  23. }
  24. x.Number = Size(x.Left) + Size(x.Right) + 1;
  25. return x;
  26. }
  27.  
  28. private Node GetMinNode(Node x)
  29. {
  30. if (x.Left == null) return x;
  31. else return GetMinNode(x.Left);
  32. }

以上二叉查找树的删除节点的算法不是完美的,因为随着删除的进行,二叉树会变得不太平衡,下面是动画演示。

三 分析

二叉查找树的运行时间和树的形状有关,树的形状又和插入元素的顺序有关。在最好的情况下,节点完全平衡,从根节点到最底层叶子节点只有lgN个节点。在最差的情况下,根节点到最底层叶子节点会有N各节点。在一般情况下,树的形状和最好的情况接近。

在分析二叉查找树的时候,我们通常会假设插入的元素顺序是随机的。对BST的分析类似与快速排序中的查找:

BST中位于顶部的元素就是快速排序中的第一个划分的元素,该元素左边的元素全部小于该元素,右边的元素均大于该元素。

对于N个不同元素,随机插入的二叉查找树来说,其平均查找/插入的时间复杂度大约为2lnN,这个和快速排序的分析一样,具体的证明方法不再赘述,参照快速排序。

四 总结

有了前篇文章 二分查找的分析,对二叉查找树的理解应该比较容易。下面是二叉查找树的时间复杂度:

它和二分查找一样,插入和查找的时间复杂度均为lgN,但是在最坏的情况下仍然会有N的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。我们追求的是在最坏的情况下仍然有较好的时间复杂度,这就是后面要讲的平衡查找树的内容了。下文首先讲解平衡查找树的最简单的一种:2-3查找树。

希望本文对您了解二叉查找树有所帮助。

附上我的代码

  1. public class BST {
  2. public TreeNode search(TreeNode root, int val) {
  3. if(root == null) {
  4. return root;
  5. }
  6. if(root.val == val) {
  7. return root;
  8. } else if(root.val > val) {
  9. return search(root.left, val);
  10. } else {
  11. return search(root.right, val);
  12. }
  13. }
  14.  
  15. public TreeNode insert(TreeNode root, int val) {
  16. if(root == null) {
  17. TreeNode node = new TreeNode(val);
  18. return node;
  19. }
  20. if(root.val > val) {
  21. root.left = insert(root.left, val);
  22. } else if(root.val < val) {
  23. root.right = insert(root.right, val);
  24. }
  25. return root;
  26. }
  27.  
  28. public TreeNode delete(TreeNode root, int val) {
  29. if(root.val > val) {
  30. root.left = delete(root.left, val);
  31. } else if(root.val < val) {
  32. root.right = delete(root.right, val);
  33. } else {
  34. if(root == null) {
  35. return null;
  36. }
  37. if(root.left == null && root.right == null) {
  38. return null;
  39. }
  40. if(root.left == null) {
  41. return root.right;
  42. }
  43. if(root.right == null) {
  44. return root.left;
  45. }
  46. TreeNode temp = root;
  47. root = getSuccessor(root);
  48. root.right = deleteMin(root.right);
  49. root.left = temp.left;
  50. }
  51. return root;
  52. }
  53.  
  54. public TreeNode deleteMin(TreeNode root) {
  55. if(root.left != null) {
  56. root.left = deleteMin(root.left);
  57. } else {
  58. return root.right;
  59. }
  60. return root;
  61. }
  62.  
  63. public TreeNode getSuccessor(TreeNode root) {
  64. root = root.right;
  65. while(root.left != null) {
  66. root = root.left;
  67. }
  68. return root;
  69. }
  70. }

二叉查找树的查找、插入和删除 - Java实现的更多相关文章

  1. 二叉平衡树AVL的插入与删除(java实现)

    二叉平衡树 全图基础解释参考链接:http://btechsmartclass.com/data_structures/avl-trees.html 二叉平衡树:https://www.cnblogs ...

  2. 二叉搜索树Java实现(查找、插入、删除、遍历)

    由于最近想要阅读下 JDK1.8 中 HashMap 的具体实现,但是由于 HashMap 的实现中用到了红黑树,所以我觉得有必要先复习下红黑树的相关知识,所以写下这篇随笔备忘,有不对的地方请指出- ...

  3. 二叉查找树(查找、插入、删除)——C语言

    二叉查找树 二叉查找树(BST:Binary Search Tree)是一种特殊的二叉树,它改善了二叉树节点查找的效率.二叉查找树有以下性质: (1)若左子树不空,则左子树上所有节点的值均小于它的根节 ...

  4. 数据结构Java实现03----单向链表的插入和删除

    文本主要内容: 链表结构 单链表代码实现 单链表的效率分析 一.链表结构: (物理存储结构上不连续,逻辑上连续:大小不固定)            概念: 链式存储结构是基于指针实现的.我们把一个数据 ...

  5. 数据结构与算法->树->2-3-4树的查找,添加,删除(Java)

    代码: 兵马未动,粮草先行 作者: 传说中的汽水枪 如有错误,请留言指正,欢迎一起探讨. 转载请注明出处. 目录 一. 2-3-4树的定义 二. 2-3-4树数据结构定义 三. 2-3-4树的可以得到 ...

  6. Java创建二叉搜索树,实现搜索,插入,删除操作

    Java实现的二叉搜索树,并实现对该树的搜索,插入,删除操作(合并删除,复制删除) 首先我们要有一个编码的思路,大致如下: 1.查找:根据二叉搜索树的数据特点,我们可以根据节点的值得比较来实现查找,查 ...

  7. 数据结构Java实现02----单向链表的插入和删除

    文本主要内容: 链表结构 单链表代码实现 单链表的效率分析 一.链表结构: (物理存储结构上不连续,逻辑上连续:大小不固定)            概念: 链式存储结构是基于指针实现的.我们把一个数据 ...

  8. AVL树(查找、插入、删除)——C语言

    AVL树 平衡二叉查找树(Self-balancing binary search tree)又被称为AVL树(AVL树是根据它的发明者G. M. Adelson-Velskii和E. M. Land ...

  9. 【线性表基础】顺序表和单链表的插入、删除等基本操作【Java版】

    本文表述了线性表及其基本操作的代码[Java实现] 参考书籍 :<数据结构 --Java语言描述>/刘小晶 ,杜选主编 线性表需要的基本功能有:动态地增长或收缩:对线性表的任何数据元素进行 ...

随机推荐

  1. hdu 1381 Crazy Search

    题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=1381 Crazy Search Description Many people like to sol ...

  2. hdu 1718 Rank

    题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=1718 Rank Description Jackson wants to know his rank ...

  3. 请输入正确的RSA公钥

    没啥原因,换个浏览器就好使,原因是现有窗口登陆超时了.

  4. 网络爬虫by pluskid

    网络爬虫(Web Crawler, Spider)就是一个在网络上乱爬的机器人.当然它通常并不是一个实体的机器人,因为网络本身也是虚拟的东西,所以这个“机器人”其实也就是一段程序,并且它也不是乱爬,而 ...

  5. java提高篇-----详解java的四舍五入与保留位

    转载:http://blog.csdn.net/chenssy/article/details/12719811 四舍五入是我们小学的数学问题,这个问题对于我们程序猿来说就类似于1到10的加减乘除那么 ...

  6. C++ vector介绍

    <span style="font-family: Arial; ">在此总结一下模板vector的使用介绍</span> 标准库vector类型使用需要的 ...

  7. [vsftp服务]——ftp虚拟用户、权限设置等的实验

    搭建ftp服务器,满足以下要求: 1.允许匿名用户登录服务器并下载文件,下载速度设置为最高2MB/s 2.不允许本地用户登录ftp服务器 3.在服务器添加虚拟用户vuser01.vuser02.vus ...

  8. 新安装Ubuntu加载时提示“为/检查磁盘时发生严重错误”的解决方法

    本文部分内容转载自: http://jingyan.baidu.com/article/0aa22375bbffbe88cc0d6419.html http://www.aichengxu.com/v ...

  9. 微软职位内部推荐-Senior SDE

    微软近期Open的职位: Position: Senior SDE-- Mobile Products Android/iOS/WP Senior Developer Contact Person: ...

  10. cocos2dx 2.0+ 版本,IOS6.0+设置横屏

    使用cocos2dx 自带的xcode模板,是不能正常的设置为横屏的. 一共修改了三个地方: 在项目属性中:Deployment Info中,勾选上 Landscape left,以及 Landsca ...