平衡二叉树AVL也是一个二叉搜索树,所以二叉搜索树的一些方法对他也适用。他跟二叉搜索树的区别在于他的每次插入,删除操作,都要校验插入或删除的节点,是否造成了树的不平衡,如果不平衡了就要适当调整他。
AVL的定义和旋转的定义就不插了,网上一搜一大片,贴一个百科地址把http://baike.baidu.com/view/671745.htm
图解简单说一下几种旋转:
LL旋转(顺时针旋转,由于在T节点的左子树的左子树上插入节点,导致了T节点的平衡因子改变而发生的旋转,),本质上,就是因为T节点的左子树加深了一层而导致了T节点的不平衡,所以用T节点的左子树代替T节点,将T节点左子树的右子树赋值给T节点当左子树,T节点变成原来T节点的右子树,如下图(算法导论):
单看图很难理解怎么编码,把LL旋转(顺时针旋转)转换成4步走,还是看图(自己画的,转载请说明,谢谢):
I 首先肯定将值value插入相应的位置,与二叉排序树插入完全一样。
II k1指向t的左孩子,然后断开k1和t
III 把k1的右孩子(大于k1,小于t)送给t当左孩子
IV t当k1的右孩子
对应I II III IV,很容易写出对应的旋转代码:
/** * rotateWithLeftChild: 带左子树旋转,适用于LL型(顺时针旋转) * * @param k2 * @return AVLNode 返回类型 */ private AVLNode<T> rotateWithLeftChild(AVLNode<T> t) { //定义临时节点k1,指向T的左孩子,旋转后替代T的位置 AVLNode<T> k1 = t.leftChild; //将t(失衡节点)的左孩子指向k1(旋转后的t位置)的右孩子 t.leftChild = k1.rightChild; //k1的右孩子位置指向t k1.rightChild = t; //以上,旋转完成,下边两行更新旋转的两个节点的高度值 //为啥能这么写,从图里可以看到,其实t的左右孩子的高度,和k1左右孩子的高度,是没有变的 t.height = Math.max(height(t.leftChild), height(t.rightChild)) + 1; k1.height = Math.max(height(k1.leftChild), t.height) + 1; return k1; }
理解了LL旋转,RR旋转就很容易理解了。 至于LR和RL,其本质就是先L旋转成RR型,然后再RR旋转,代码都不用写,两次旋转即可。
查询不比说了。。 就是排序树的查询。
删除,类似排序树,也有一个简单的方法,就是给每个节点赋一个boolean值,删除时把该值置为false。
一般性删除即:
1) 找出该节点的位置T,
2) 找到T节点的子树中,较大的子树(这样做的目的是,用交大子树的节点替换该节点删除后,该节点为根节点组成的树,本身肯定是一颗平衡二叉树,不用对该节点再做平衡判断了)
3)找出2)中要替换的节点(如果T的左子树较高,那取T左子树的最大节点),把该节点的值与T替换,删除该节点;
4) 递归向上判断删除T节点后,树是否产生了不平衡状态,做相应调整(如果平衡因子变成2,说明因为删除右子树导致不平衡(相当于插入左子树),进行LL旋转,反之亦然)。
java代码:
/** * remove: 删除avl树中的节点 * @param node 要删除的节点所在的根节点 * @param value 要删除的value * @return * AVLNode 返回类型 */ private AVLNode<T> remove(AVLNode<T> node, T value) { int cmp = value.compareTo(node.value); if (cmp < 0) { // 待删除的节点在"node的左子树"中 node.leftChild = remove(node.leftChild, value); // 删除节点后,若AVL树失去平衡,则进行相应的调节。 if (height(node.rightChild) - height(node.leftChild) == 2) { AVLNode<T> delNode = node.rightChild; if (height(delNode.leftChild) > height(delNode.rightChild)) node = rotateWithLeftChild(node); else node = rotateWithRightChild(node); } } else if (cmp > 0) { // 待删除的节点在"tree的右子树"中 node.rightChild = remove(node.rightChild, value); // 删除节点后,若AVL树失去平衡,则进行相应的调节。 if (height(node.leftChild) - height(node.leftChild) == 2) { AVLNode<T> delNode = node.leftChild; if (height(delNode.rightChild) > height(delNode.leftChild)) node = rotateWithLeftChild(node); else node = rotateWithRightChild(node); } } else { // 删除节点代码在这里,上面都是删除后的调整代码。 // 要删除的节点node的左右孩子都非空 if ((node.leftChild != null) && (node.rightChild != null)) { if (height(node.leftChild) > height(node.rightChild)) { // 如果node的左子树比右子树高; // 则首先找出node的左子树中的最大节点 // 第二步将该最大节点的值赋值给node。 // 最后删除该最大节点。 // 这类似于用"node的左子树中最大节点"做"node"的替身; // 采用这种方式的好处是:删除"node的左子树中最大节点"之后,AVL树仍然是平衡的,这样可以免去很多不必要的麻烦。 T max = this.getMax(node.leftChild); node.value = max; node.leftChild = remove(node.leftChild, max); } else { //与上面操作类似 T min = this.getMin(node.rightChild); node.value = min; node.leftChild = remove(node.rightChild, min); } } else { node = (node.leftChild != null) ? node.leftChild : node.rightChild; } } return node; }
测试代码:
/** * main: 主函数 * * @param args * void 返回类型 */ public static void main(String[] args) { // TODO Auto-generated method stub AVLTree<Integer> tree = new AVLTree<Integer>(2); tree.insert(1); tree.insert(2); tree.insert(3); tree.insert(4); tree.insert(5); tree.insert(6); TreeTools.levelTravel(tree.root); System.out.println("\n"+tree.searchNode(4)); System.out.println(tree.isAVL()); tree.remove(3); TreeTools.levelTravel(tree.root); System.out.println("\n"+tree.searchNode(3)); System.out.println(tree.isAVL()); TreeTools.midOrderTravel(tree.root); }
测试结果:
4 2 5 1 3 6 true true 4 2 5 1 6 false true 1 2 4 5 6