二叉查找树也叫二叉排序树,二叉搜索树, 它具备以下特性:
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 }
测试结果: