倒霉的菜鸟

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

二叉查找树也叫二叉排序树,二叉搜索树, 它具备以下特性:

1)可以是一颗空树

2)如果不是空树,那么每个节点左子树的值都比该节点小;右子树的值都比该节点大

3)左右子树都为二叉树

4)原则上没有重复值(实际应用中如需要有重复值可忽略)

接下来我们来试着定义一棵二叉查找树

首先定义节点内部类:

 1 /**
 2      * 定义节点内部类
 3      */
 4     static class TreeNode{
 5         //数据域, 这里的数据域可以是实现了equals,compareTo的任意类型
 6         //这里为了方便,定义为Int
 7         private int data;
 8 
 9         //左孩子
10         private TreeNode leftChild;
11 
12         //右孩子
13         private TreeNode rightChild;
14 
15         //为了插入、删除方便, 我们采用孩子双亲表示法来描述节点
16         private TreeNode parent;
17 
18         public TreeNode(int data){
19             this.data = data;
20             this.leftChild = null;
21             this.rightChild = null;
22             this.parent = null;
23         }
24     }

增加节点:

 1 //根节点
 2     private TreeNode root;
 3 
 4     /**
 5      * 增加节点
 6      */
 7     public void put(int data){
 8         TreeNode newNode = new TreeNode(data);
 9         //如果当前是一颗空树,那么新加的节点就是根节点
10         if (root == null){
11             root = newNode;
12         }else {
13          //否则就从根节点开始遍历
14             TreeNode node = root;
15             //跟踪记录parent
16             TreeNode parent = node.parent;
17             //结合下面代码, node根据data的大小向左或向右移动
18             //直到找到一个空位置
19             while(node!= null){
20                 parent = node;
21                 //比当前节点小往左走
22                 if (data < node.data){
23                     node = node.leftChild;
24 
25                 }else if (data > node.data) {
26                     //比当前节点大往右走
27                     node = node.rightChild;
28                 }else {
29                     //如果相等,说明有重复值
30                     return;
31                 }
32             }
33             //while循环结束, node定位在一个空位置
34             //我们把新节点放在这里, 根据parent来引用他
35             newNode.parent = parent;
36             //通过比较确定新节点是作为parent的leftchild还是rightchild
37             if(data < parent.data){
38                 parent.leftChild = newNode;
39             }else {
40                 parent.rightChild = newNode;
41             }
42         }
43     }

查询节点

 1 /**
 2      * 查找某个Int类型的data是否存在
 3      * 存在则返回该数字
 4      * 不存在返回-1
 5      */
 6     public TreeNode search(int data){
 7         //从根节点开始
 8        TreeNode node = root;
 9        while(node!=null){
10            if(data == node.data){
11                return node;
12            }else if(data < node.data){
13                //往左边走
14                node = node.leftChild;
15            }else {
16                node = node.rightChild;
17            }
18        }
19         return null;
20     }

删除节点

第一步,判断节点是否存在

 1 /**
 2      * 删除节点
 3      */
 4     public void delete(int data){
 5        //先找到节点
 6         TreeNode targetNode = search(data);
 7        if(targetNode == null){
 8            return;
 9        }
10        deleteNode(targetNode);
11     }

第二步,执行删除。这里比较复杂

需要分4种情况来处理

1,要删除的节点是叶子,没有左儿子或右儿子, 那么可以直接删除

  1 /**
  2      * 删除节点分为4种情况
  3      * @param node
  4      * @return
  5      */
  6     private void deleteNode(TreeNode node){
  7         //找到它的父节点,左儿子, 右儿子
  8         TreeNode parent  = node.parent;
  9         TreeNode nodeLeftChild = node.leftChild;
 10         TreeNode nodeRightChild = node.rightChild;
 11         int data = node.data;
 12         //case1, 如果要删除的节点没有孩子,可以直接删除
 13         if (nodeLeftChild == null && nodeRightChild == null){
 14             if(parent == null){
 15                 //说明要删除的是根节点,整个树上只有这一个节点
 16                 root = null;
 17                 return;
 18             }
 19             //如果它是左孩子,就将父节点的左孩子置空
 20             if(data < parent.data){
 21                 parent.leftChild = null;
 22             }else{
 23                 //反之亦然
 24                 parent.rightChild = null;
 25             }
 26         }
 27     }

2, 如果要删除的节点只有一个左儿子,则它被删除后,左儿子顶上去

 1 else if (nodeLeftChild!= null && nodeRightChild==null){
 2             //case2, 要删除的节点只有一个左孩子
 3             //如果是根节点
 4             if (parent == null){
 5                 root = nodeLeftChild;
 6                 root.parent = null;
 7                 return;
 8             }
 9             //如果它是左孩子,就将它的leftChild赋值给父节点的左孩子
10             if(data < parent.data){
11                 parent.leftChild = nodeLeftChild;
12                 nodeLeftChild.parent = parent;
13             }else{
14                 //反之亦然
15                 parent.rightChild = nodeLeftChild;
16                 nodeLeftChild.parent = parent;
17             }
18         }

3, 如果要删除的节点只有一个右儿子,则它被删除后,右儿子顶上去

else if (nodeLeftChild == null && nodeRightChild!=null){
            //case3, 如果要删除的节点只有一个右孩子,那么让右孩子顶上去
            //如果是根节点
            if (parent == null){
                root = nodeRightChild;
                root.parent = null;
                return;
            }
            //如果它是左孩子,就将它的rightChild赋值给父节点的左孩子
            if(data < parent.data){
                parent.leftChild = nodeRightChild;
                nodeRightChild.parent = parent;
            }else{
                //反之亦然
                parent.rightChild = nodeRightChild;
                nodeRightChild.parent = parent;
            }

        }

4, 如果要删除的节点既有左孩子也有右孩子

这就是最复杂的情况了, 比如我们要删除x节点, 那么首先要在x的右子树上找到一个最小值节点替换它

鉴于二叉排序树的特性, 我们知道这个节点就是x的右子树的左子树一直往左遍历,最后一个节点,我们把它标记为leftNode

接下来, 1)让原本x的左子树成为leftNode 的左子树

2) leftNode如果有右子树,让它成为leftNode的父节点的左子树

3)让原本x的右子树成为leftNode的右子树

4)用leftNode替换x(即leftNode.parent = x.parent)

 

 

 结合上图,看下代码实现

先从简单的入手,处理根节点的情况

 1 if (parent == null){
 2                 //先处理要删除的是根节点的情况
 3                 //把minLeftNode这个节点独立出来
 4                 TreeNode targetLeftNodeParent = targetNode.parent;
 5                 targetLeftNodeParent.leftChild = null;
 6                 targetNode.parent = null;
 7                 //它的右儿子(如果有)就成为它的父节点的左儿子
 8                 if (minLeftNode.rightChild != null){
 9                     targetLeftNodeParent.leftChild = targetNode.rightChild;
10                 }
11                 //让这个节点成为根节点
12                 root = targetNode;
13                 //重新构建它和原node的子节点的关系
14                 root.rightChild = nodeRightChild;
15                 nodeRightChild.parent = root;
16                 root.leftChild = nodeLeftChild;
17                 nodeLeftChild.parent = root;
18             }

非根节点的情况

 1 else {
 2                 //step 1
 3                 targetNode.leftChild = nodeLeftChild;
 4                 nodeLeftChild.parent = targetNode;
 5                 //step2
 6                 TreeNode targetLeftNodeParent = targetNode.parent;
 7                 //它的右儿子(如果有)就成为它的父节点的左儿子
 8                 if (minLeftNode.rightChild != null && minLeftNode.data < targetLeftNodeParent.data){
 9                     targetLeftNodeParent.leftChild = targetNode.rightChild;
10                 }
11                 //step 3
12                 if (minLeftNode.rightChild != null && minLeftNode.data < targetLeftNodeParent.data){
13                     targetNode.rightChild = nodeRightChild;
14                 }
15                 //step 4
16                 if(targetNode.data < parent.data){
17                     //那么它就是左儿子
18                     parent.leftChild = targetNode;
19                 }else {
20                     parent.rightChild = targetNode;
21                 }
22                 targetNode.parent = parent;
23             }
 1 /**
 2      * 找到root节点左子树中的最小值
 3      * @param root
 4      * @return
 5      */
 6     private TreeNode findMinLeftNode(TreeNode root){
 7         TreeNode node = root;
 8         while (node.leftChild != null){
 9             node = node.leftChild;
10         }
11         return node;
12     }

二叉排序树的遍历

 1   /**
 2      * 测试需要, 定义一个方法来打印所有的节点
 3      * 传入根节点
 4      */
 5     public void testPrint(){
 6         printAllNodes(root);
 7 //        printAllNotesByPost(root);
 8     }
 9 
10     private void printAllNodes(TreeNode node){
11         //这里用二叉树的中序遍历, 可以打印出有序的数组
12         //当然也可以用前序或者后序
13         if(node == null){
14             return;
15         }
16         printAllNodes(node.leftChild);
17         System.out.print(node.data+" ");
18         printAllNodes(node.rightChild);
19     }
20 
21     /**
22      * 测试后序遍历
23      */
24     private void printAllNotesByPost(TreeNode node){
25         if(node == null){
26             return;
27         }
28         printAllNotesByPost(node.leftChild);
29         printAllNotesByPost(node.rightChild);
30         System.out.print(node.data+" ");
31     }

测试方法: 

 1 @Test
 2     fun testTree(){
 3 //        val array = intArrayOf(8,7,10,5,3,9,13,2,4,6,11,12)
 4         val array = intArrayOf(5,2,7,3,4,1,6)
 5         val binarySearchTree = BinarySearchTree()
 6         for (i in array) {
 7             binarySearchTree.put(i)
 8         }
 9         binarySearchTree.testPrint();
10         //测试查找
11 //        for (i in array) {
12 //            System.out.println(binarySearchTree.search(i))
13 //        }
14         //循环删除
15         for (i in array) {
16             System.out.println("------------------"+i)
17             binarySearchTree.delete(i)
18             binarySearchTree.testPrint()
19         }
20     }

测试结果:

 

 

 

posted on 2021-10-10 20:47  倒霉的菜鸟  阅读(52)  评论(0编辑  收藏  举报