二叉查找树的懒惰删除(lazy deletion)
第四章习题:二叉查找树类实现懒惰删除,注意findMin()和findMax()(递归)
算是发布的第一篇学习笔记。也不敢保证写的代码一定正确,错了的地方请大家指正,谢谢。
直接开始吧。先谈谈数据结构,二叉查找树懒惰删除较于一般的二叉查找树,多了一些域:theSize(剩下的节点数)、deletedSize(懒惰删除的节点数)、BinaryNode<AnyType> min,max(用于保留在findMin和findMax方法中递归查询到的flag!=1的最值点);在内部节点类中,多了一个byte型的flag变量(=1则表示被删除)。在这里,也可以使用一个count域,这在有重复项时很常用,初始的count都是1,以后有插入就+1,有删除且count>0就-1.我使用的直接是flag标志变量。
1 class myLazyBinarySearchTree<AnyType extends Comparable<? super AnyType>> { 2 private BinaryNode<AnyType> root; 3 private int theSize;// 总节点数 4 private int deletedSize;// 懒惰删除的节点数 5 private BinaryNode<AnyType> min = null; 6 private BinaryNode<AnyType> max = null; 7 8 public myLazyBinarySearchTree() { 9 root = null; 10 this.deletedSize = 0; 11 this.theSize = 0; 12 } 13 14 public boolean isEmpty() { 15 return theSize - deletedSize == 0; 16 } 17 }
其中contains()比较简单,在递归查找中找到后再查看一下节点的flag变量即可;insert例程也一样,如果有是已存在元素,查看其flag变量是否=1。
下面是比较麻烦的remove()方法,实现方法和之前的LinkedList一样,先是递归删除,未找到直接return,找到了先将节点flag=1,改变theSize和deletedSize,然后比较theSize和deletedSize的值(懒惰删除的数目>=剩下的节点数目),进行标准删除。标准删除从root开始。
在标准删除中,如果节点不需要删除,直接检查其左右子树。如果检测到需要删除的节点,再检测①若只存在左子树或者右子树,则将子树拼接上来,重新检测该位置节点。②若是叶子节点,直接置Null删除。③若有左子树也右子树,则同理先将右子树找到一个最小的且flag=0的子树,这里又分为两种情况,若右子树不存在这样一个最小节点,则直接将该右子树置null,并重新检测该节点,若找到了则调整标志等。删除实现如下:
1 public void remove(AnyType x) { 2 remove(x, root);// 这里递归删除只能使用void在下面的remove的else中,t随着变化,而懒惰删除中,只有theSize<=deletedSize才变化,所以t时钟指向的root.造成错误。 3 4 } 5 6 private void remove(AnyType x, BinaryNode<AnyType> t) { 7 if (t == null) { 8 // 未找到 9 return; 10 } 11 12 int compareResult = x.compareTo(t.element); 13 if (compareResult < 0) { 14 remove(x, t.left); 15 } else if (compareResult > 0) { 16 remove(x, t.right); 17 } else { 18 // 找到了这个节点,标记删除 19 t.flag = 1; 20 this.theSize--; 21 this.deletedSize++; 22 checkDeletion();// 检测是否进行标准删除 23 System.out.println("root1:" + this.root.right); 24 } 25 } 26 27 private void checkDeletion() { 28 if (this.theSize <= this.deletedSize) { 29 this.root = doRemove(root); 30 // 更新节点 31 this.deletedSize = 0; 32 } 33 } 34 35 private BinaryNode<AnyType> doRemove(BinaryNode<AnyType> t) {// 删除树中所有flag=1的节点。使用递归 36 37 if (t == null) { 38 return null; 39 } 40 41 if (t.flag == 0) {// 不需要删除的节点,直接进入左右子树 42 //System.out.println("000"); 43 t.left = doRemove(t.left); 44 t.right = doRemove(t.right); 45 } else { 46 if (t.left == null || t.right == null) {// 叶子节点或者只有一个子树 47 t = (t.left != null) ? t.left : t.right; 48 t = doRemove(t);// 检查拼接上来的子树 49 } else {// ②左右子树都存在 50 min = null; 51 findMin(t.right); 52 if (min == null) {// 右子树上所有节点都被懒惰删除,直接将t的右子树删除,并继续监测t 53 t.right = null; 54 t = doRemove(t); 55 } else { 56 t.element = min.element; 57 t.flag = 0; 58 min.flag = 1; 59 t = doRemove(t); 60 } 61 62 } 63 } 64 return t; 65 }
在删除需要查找右子树中最小数值节点且其flag=0,即是未被删除的最小节点。在这儿可以参考二叉树中序遍历的实现,可以利用栈后进先出的特点。为了简明易懂,我使用的是递归查找,并使用了一个前面定义的全局变量进行存储最小元。
1 private void findMin(BinaryNode<AnyType> t) { 2 if (t != null) { 3 findMin(t.left); 4 if (t.flag == 0 && min == null) { 5 min = t; 6 return; 7 } 8 findMin(t.right); 9 } 10 }
查找最大值方法类似。在删除的过程中,每次选择右子树最小元素来代替被删除的元素容易造成树的不平衡性(见P91),因而可以考虑进行随机删除,即随机算则右子树的最小元素或者左子树的最大元素,来消除树的偏向。