java实现二叉查找树(插入、删除、遍历、查找)

 闲话:

      继续撸数据结构和算法。看数据结构推荐一个可视化工具吧(http://visualgo.net/),没有图凭脑袋想是很痛苦的。

      正文:

      二叉查找树,也叫二叉搜索树、有序二叉树,排序二叉树,满足以下性质(非严谨描述):

      1.对于每个节点,其左子节点要么为空,要么值小于该节点值。

      2.对于每个节点,其右子节点要么为空,要么值大于该节点值。

      3.没有键值相等的点。

      通俗的归纳一下性质,二叉查找树中每个节点的值都大于其左子节点,小于其右子节点(如果左右子节点存在的话)。所以二叉查找树中每个节点的左边,整棵左树都是小于它的节点;右边,整棵右树都是大于它的节点。

      例图:

      

      基于这样的特性,查找的时候就很好操作了,从根节点开始,查找,如果值大于节点值,往右找;如果值小于节点值,往左找;如果值刚好相等,就找到了。是不是看着就能写出代码了?这种查找过程很像二分查找法,但是那个是数组结构,这个是树结构。

      二叉查找树的操作基本概括为:插入值,删除值,查找值以及二叉树的遍历。

      这里面,删除是最麻烦的。

      (本来觉得写数据结构还是用c语言最好的,直接可以操作指针,清晰明了效率高,但是c确实丢了太久了,而且现在主要目的是温习数据结构和算法的知识,所以只能放弃用c的想法,以后如果需要再学习,先用最熟悉的java来实现代码)

      下面来看具体的操作和逻辑,附带贴上代码。

      首先是准备工作,java写,没指针,只有利用引用了,节点类是少不了的:

[java]  view plain  copy
  1. /** 
  2.      * 节点 
  3.      *  
  4.      * @author zhangyu 
  5.      * 
  6.      * @param <T> 
  7.      */  
  8.     public class Node<T extends BaseData> {  
  9.         // 节点数据  
  10.         T data;  
  11.         // 父节点,左右子节点  
  12.         Node<T> fatherNode, leftChildNode, rightChildNode;  
  13.         //是否是左节点、是否是右节点  
  14.         boolean isLeftChild = false, isRightChild = false;  
  15.   
  16.         //左节点是否存在  
  17.         public boolean haveLeftChild() {  
  18.             return !(leftChildNode == null);  
  19.         }  
  20.   
  21.         //右节点是否存在  
  22.         public boolean haveRightChild() {  
  23.             return !(rightChildNode == null);  
  24.         }  
  25.   
  26.         //构造方法  
  27.         public Node(boolean isLeft, boolean isRight) {  
  28.             isLeftChild = isLeft;  
  29.             isRightChild = isRight;  
  30.         }  
  31.     }  
       然后是插入操作,根据特性,逻辑和查找差不多,从根节点比较,小于则继续比较其左节点;大于则比较其右节点;直到当某节点左或右节点为空时,在空值处,插入新节点。

      例图(插入65,黄色线为比较的轨迹):

      

      代码:

[java]  view plain  copy
  1. /** 
  2.      * 插入节点 
  3.      *  
  4.      * @param insertData 待插入的数据 
  5.      * @param node 开始比较的节点 
  6.      */  
  7.     private void insertNode(T insertData, Node<T> node) {  
  8.   
  9.         int compareResult = insertData.compareTo(node.data);  
  10.         if (compareResult == 0)// 相等  
  11.             return;  
  12.         else if (compareResult > 0) {// 大于节点值  
  13.             if (node.rightChildNode == null) {  
  14.                 node.rightChildNode = new Node<T>(falsetrue);  
  15.                 node.rightChildNode.data = insertData;// 插入值  
  16.                 node.rightChildNode.fatherNode = node;  
  17.                 return;  
  18.             } else  
  19.                 insertNode(insertData, node.rightChildNode);// 继续对比右子节点  
  20.         } else {// 小于节点值  
  21.             if (node.leftChildNode == null) {  
  22.                 node.leftChildNode = new Node<T>(truefalse);  
  23.                 node.leftChildNode.data = insertData;// 插入值  
  24.                 node.leftChildNode.fatherNode = node;  
  25.                 return;  
  26.             } else  
  27.                 insertNode(insertData, node.leftChildNode);// 继续对比左子节点  
  28.         }  
  29.     }  
  30.   
  31.     /** 
  32.      * 插入节点 
  33.      *  
  34.      * @param insertData 待插入的数据 
  35.      */  
  36.     public void insertNode(T insertData) {  
  37.         if (treeRoot.data == null) {  
  38.             treeRoot.data = insertData;  
  39.             return;  
  40.         }  
  41.         insertNode(insertData, treeRoot);  
  42.     }  
       然后来看查询操作,跟插入逻辑几乎是一样的,直接看代码吧:

[java]  view plain  copy
  1. /** 
  2.      * 从某个节点开始搜索 
  3.      *  
  4.      * @param target 目标值 
  5.      * @param startSearchNode 开始搜索的节点 
  6.      * @return 
  7.      */  
  8.     public Node searchNode(T target, Node startNode) {  
  9.         int compareResult = target.compareTo(startNode.data);  
  10.   
  11.         if (compareResult == 0)  
  12.             return startNode;  
  13.         else if (compareResult > 0 && startNode.rightChildNode != null)  
  14.             return searchNode(target, startNode.rightChildNode);  
  15.         else if (compareResult < 0 && startNode.leftChildNode != null)  
  16.             return searchNode(target, startNode.leftChildNode);  
  17.         else  
  18.             return null;  
  19.     }  
  20.   
  21.     /** 
  22.      * 查找数据所在节点 
  23.      *  
  24.      * @param target 目标数据 
  25.      * @return null或数据所在节点 
  26.      */  
  27.     public Node searchNode(T target) {  
  28.         if (treeRoot.data == null)  
  29.             return null;  
  30.         return searchNode(target, treeRoot);  
  31.     }  
  32.   
  33.     /** 
  34.      * 查找数据 
  35.      * @param target 目标数据(有部分检索需要的信息即可) 
  36.      * @return 完整目标数据 
  37.      */  
  38.     public BaseData searchData(T target) {  
  39.         Node node = searchNode(target);  
  40.         if (node != null)  
  41.             return node.data;  
  42.         return null;  
  43.     }  

      然后看删除操作,这个删除真的是有点麻烦,为了把这部分理清楚,把代码调通,几乎花费了一整天的时间,慢慢捋来~

    删除分为以下几种情况:

    1.被删除的节点只有左节点或者只有右节点,这种情况好办,因为节点在一条链上,没有分叉,就像处理链表一样把这个节点摘掉就行了。让它的父节点关联它的子节点,它的子节点关联它的父节点就完事。如果它没有父节点,说明它是根节点,直接将其子节点作为根节点就行。

    2.被删除的节点没有子节点,这种情况也很简单,它是叶子节点,直接置空,将其父节点对应的子节点也置空,就完事。

   3.被删除的节点有左右子节点。这种情况就有点麻烦了。

    这里需要了解两个概念,叫“前驱”和“后继”。分别是树中小于它的最大值和大于它的最小值,如果把树结构中的所有节点按顺序拍好的话,它的前驱和它的后继两个节点刚好在它左右紧挨着它。当一个节点被删除时,为了保证二叉树的结构不被破坏,要让它的前驱或者后继节点来代替它的位置,然后将它的前驱或者后继节点同样做删除操作。

    那么怎样找前驱或者后继呢。小于它的最大值,就是在树中在它左边最靠右的那个节点。同样,大于它的最小值,就是在树中在它右边最靠左的那个节点。当一个节点既有左子节点又有右子节点时,前驱就是它的左子节点的右子节点的右子节点...直到最右子节点;后继就是它的右子节点的左子节点的左子节点...直到最左子节点。上个图吧:

    23的后继是32;98的前驱是76。


    上图中,当23被删除,则可以用32代替它,也可以用12代替它,然后删除掉32(或者12)的原节点就行了;同理,当98被删除时,可以用76代替它,或者用99代替它,然后删除76(或者99)的原节点就行了。当然,如果被删除节点是根节点,就用代替它的节点作为根节点然后删除代替节点的原节点就行了。再次推荐你用可视化工具http://zh.visualgo.net/bst来看二叉树的各种操作动画,简单明了。

    所以删除操作代码如下(代码用的是后继节点替代待删除节点):

[java]  view plain  copy
  1. /** 
  2.      * 删除节点 
  3.      *  
  4.      * @param node 待删除节点 
  5.      */  
  6.     private void deleteNode(Node node) {  
  7.         // 如果按顺序排列好节点,它的前驱和后继就是这个序列上紧挨着它左右两侧的节点.  
  8.   
  9.         // 如果节点只有左节点或者只有右节点  
  10.   
  11.         if (node.haveLeftChild() && !node.haveRightChild()) {// 只有左节点  
  12.             if (node.isLeftChild) {  
  13.                 node.fatherNode.leftChildNode = node.leftChildNode;  
  14.   
  15.             } else if (node.isRightChild) {  
  16.                 node.fatherNode.rightChildNode = node.leftChildNode;  
  17.             } else// 待删除节点是根节点  
  18.                 treeRoot = node.leftChildNode;  
  19.             node.leftChildNode.fatherNode = node.fatherNode;  
  20.         } else if (node.haveRightChild() && !node.haveLeftChild()) {// 只有右节点  
  21.             if (node.isLeftChild) {  
  22.                 node.fatherNode.leftChildNode = node.rightChildNode;  
  23.   
  24.             } else if (node.isRightChild) {  
  25.                 node.fatherNode.rightChildNode = node.rightChildNode;  
  26.             } else// 待删除节点是根节点  
  27.                 treeRoot = node.rightChildNode;  
  28.             node.rightChildNode.fatherNode = node.fatherNode;  
  29.         } else if (node.haveLeftChild() && node.haveRightChild()) {// 有左右子节点  
  30.             Node successorNode = getSuccessorNode(node);  
  31.             if (successorNode == node.rightChildNode) {// 后继节点是右子节点  
  32.                 successorNode.fatherNode = node.fatherNode;  
  33.                 if (node.isLeftChild)  
  34.                     node.fatherNode.leftChildNode = successorNode;  
  35.                 else if (node.isRightChild)  
  36.                     node.fatherNode.rightChildNode = successorNode;  
  37.                 else {// 是根节点  
  38.                     successorNode = treeRoot;  
  39.                 }  
  40.   
  41.                 successorNode.fatherNode = node.fatherNode;  
  42.                 successorNode.leftChildNode = node.leftChildNode;  
  43.                 node.leftChildNode.fatherNode = successorNode;  
  44.   
  45.             } else {// 后继节点是右子节点的最左子节点  
  46.                 if (successorNode.haveRightChild()) {// 左子节点有右子树  
  47.                     successorNode.fatherNode.leftChildNode = successorNode.rightChildNode;  
  48.                     successorNode.rightChildNode.fatherNode = successorNode.fatherNode;  
  49.   
  50.                     replaceNode(node, successorNode);  
  51.   
  52.                 } else {// 左子节点没有右子树  
  53.                         // 叶节点,直接删除  
  54.                     successorNode.fatherNode.leftChildNode = null;  
  55.                     replaceNode(node, successorNode);  
  56.                 }  
  57.             }  
  58.   
  59.         } else {// 没有子节点  
  60.             if (node.isLeftChild) {  
  61.                 node.fatherNode.leftChildNode = null;  
  62.             } else if (node.isRightChild) {  
  63.                 node.fatherNode.rightChildNode = null;  
  64.             }  
  65.   
  66.         }  
  67.   
  68.         node = null;  
  69.     }  
  70.   
  71.     /** 
  72.      * 非相邻节点的替换逻辑(非相邻加粗!) 
  73.      * @param node 被替换节点 
  74.      * @param replaceNode 替换的节点 
  75.      */  
  76.     private void replaceNode(Node node, Node replaceNode) {  
  77.         if (node.isLeftChild)  
  78.             node.fatherNode.leftChildNode = replaceNode;  
  79.         else if (node.isRightChild)  
  80.             node.fatherNode.rightChildNode = replaceNode;  
  81.         else {// node是根节点  
  82.             treeRoot = replaceNode;  
  83.         }  
  84.   
  85.         node.leftChildNode.fatherNode = node.rightChildNode.fatherNode = replaceNode;  
  86.         replaceNode.leftChildNode = node.leftChildNode;  
  87.         replaceNode.rightChildNode = node.rightChildNode;  
  88.     }  
  89.   
  90.     /** 
  91.      * 获取一个节点的后继节点 
  92.      * @param node 
  93.      * @return 
  94.      */  
  95.     private Node getSuccessorNode(Node node) {  
  96.         if (!node.haveRightChild()) {// 没有右子树  
  97.             return null;  
  98.         }  
  99.   
  100.         Node targetNode = node.rightChildNode;  
  101.         while (targetNode.haveLeftChild()) {// 找右子树的最左孩子,保证返回的节点一定没有左子树  
  102.             targetNode = targetNode.leftChildNode;  
  103.         }  
  104.   
  105.         return targetNode;  
  106.     }  
  107.   
  108.     /** 
  109.      * 删除数中的数据 
  110.      * @param baseData 
  111.      */  
  112.     public void deleteData(T baseData) {  
  113.         Node node = searchNode(baseData);  
  114.         deleteNode(node);  
  115.     }  
     然后还有一个遍历操作,中规中矩的先序遍历,递归操作:

[java]  view plain  copy
  1. /** 
  2.      * 遍历节点 
  3.      * @param node 
  4.      */  
  5.     private void preOrder(Node node) {  
  6.         System.out.println("" + node.data.toString());  
  7.         if (node.haveLeftChild())  
  8.             preOrder(node.leftChildNode);  
  9.   
  10.         if (node.haveRightChild())  
  11.             preOrder(node.rightChildNode);  
  12.     }  
  13.   
  14.     /** 
  15.      * 遍历树(前序遍历) 
  16.      */  
  17.     public void preOrder() {  
  18.         if (treeRoot == null)  
  19.             return;  
  20.   
  21.         preOrder(treeRoot);  
  22.   
  23.     }  
      二叉搜索树大概就这样吧,附上文中代码资源: 点击打开链接  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值