平衡树的删除以及保持平衡的方法思路及实现
我学习数据结构使用的书是机械工业出版社的黑皮书系列:《Data Structure And Algorithm Analysis in Java Second Editioin》在平衡树这一节中,书中附带的代码给出了一个平衡树的部分实现,其中删除节点的方法没有实现,这个我自己实现了,并且将保持平衡的功能单独封装成了一个方法。
下面先说说我的思路:
平衡二叉树的删除与保持平衡思路及实现。
平衡树中某个节点的删除情形可以分为三类:
1) 欲删除的目标节点是叶子节点
2) 欲删除的目标节点只有一颗孩子树(左或右)
3) 欲删除的目标节点有两颗孩子树
1、欲删除的目标节点是叶子节点
在这种情况中,只需要直接删除该节点就可以了,然后一路循着其父节点往上检查是否平衡。
2、欲删除的目标节点只有一颗孩子树
在这种情况中,也比较简单,只需要使用该节点的孩子节点直接代替其位置即可。然后循着其父节点一路往上检查平衡性。
3、欲删除的目标节点有两颗孩子树
在这种情况中,解决思路是这样的:所谓删除该节点,并不是移除这个节点,这会导致子树有大量的变动。所谓删除,实际上是删除该节点中的元素,而后从该节点的子树中寻找一个合适的节点中的元素来替代被删除节点中的元素。
这样一来,就可以将因为删除导致的平衡树的变换降到最低。那么如何来寻找合适的替代节点呢?
我的思路:从左右子树中较高的一侧寻找替代节点。如果是从左子树中寻找,则找其最右端的子节点,也就是左子树中最大的元素作为替代节点;如果是从右子树中寻找,则找其最左端的子节点,也就是右子树中最小的元素作为替代节点。
这样能够尽可能的降低因为删除节点对树的平衡带来的影响。
保持平衡的思路
先说说我的结论:
1、 如果根t的左孩子t.left 比t的右孩子t.right高2,那么说明以t为根的这棵树失衡了。那么在这样的情况下,分两种情况:

a) 这种最简单的失衡情况,在这种情况下t的右子树是为空的,则使用左旋转来维持平衡

b) 而第二种情况,这是t的右子树不为空,则使用右-左双旋转来维持平衡。
2、之亦然,如果t的右孩子t.right比t的左孩子t.left高2,那么说明t为根的这棵树失衡了,同样分两种情况:

a) 这种最简单的失衡情况,在这种情况下t的左子树是为空的,则使用右旋转来维持平衡


b)而第二种情况,这是t的左子树不为空,则使用左-右双旋转来维持平衡。
删除方法:包括删除功能的主体,两个用于寻找替代节点的方法,以及一个保持平衡的方法
下面是删除功能的主体
1 private AvlNode<AnyType> remove(AnyType x, AvlNode<AnyType> t) throws UnderflowException {
2
3 //1、如果节点t为空,则表示该树中不存在这个节点,抛出异常
4 if( t == null ){
5 throw new UnderflowException();
6 }
7
8 //2、判断该节点与待删除节点的关系
9 int compareResult = x.compareTo( t.element );
10
11 if( compareResult < 0 ){//3、如果待删除节点小于该节点,则在该节点的左孩子中继续查找
12
13 t.left = remove( x, t.left );
14
15 }else if( compareResult > 0 ){//4、如果待删除节点大于该节点,则在该节点的右孩子中继续查找
16
17 t.right = remove( x, t.right );
18
19 }else{//5、如果待删除节点等于该节点
20
21 //分三种情况:该节点是叶子节点、该节点只有一个孩子节点、该节点有两个孩子节点
22 if( t.left == null && t.right == null ){ //如果该节点是叶子节点,直接删除
23
24 return null;
25
26 }else if( t.left == null || t.right == null ){ //如果该节点有一个孩子节点
27
28 //如果只有一个孩子那就可以直接删除,使用其孩子节点代替其位置
29 if( t.left != null ){ //如果t的左孩子不为空
30
31 t = t.left;
32
33 }else if( t.right != null ){ //如果t的右孩子不为空
34
35 t = t.right;
36 }
37
38 }else if( t.left != null & t.right != null ){ //t有两个孩子节点
39
40 //有两个子节点,则判断左右节点哪个高,从较高的子树中寻找替代节点
41
42 if( height( t.left ) > height( t.right ) ){ //做子树比右子树高
43
44 t.element = getSubstituteFromLeftChild( x, t, t.left );
45
46 }else{
47
48 t.element = getSubstituteFromRightChild( x, t, t.right );
49 }
50 }
51 }
52
53 t.height = Math.max( height( t.left ), height( t.right ) ) + 1;
54
55 return keepBalanceIfNot( t );
56 }
下面是两个查找替代节点的方法
1 //从左子树中查找替代节点
2 private AnyType getSubstituteFromLeftChild(AnyType x, AvlNode<AnyType> parent,
3 AvlNode<AnyType> t) {
4
5 AnyType temp = null;
6
7 if( t.right != null ){
8
9 return getSubstituteFromLeftChild( x, t, t.right );
10
11 }else{
12
13 temp = t.element;
14
15 if( x.compareTo( parent.element ) == 0 ){ //如果该替代节点是被删除节点的儿子(第一代子孙),使用左孩子替代父亲的左孩子
16
17 parent.left = t.left;
18
19 }else{ //否则如果不是第一代子孙,则使用左孩子替代父亲的右孩子
20
21 parent.right = t.left;
22 }
23
24 parent.height = Math.max( height( parent.left ), height( parent.right ) ) + 1;
25
26 parent = keepBalanceIfNot( parent );
27
28 return temp;
29 }
30 }
右
1 //从右子树中查找替代节点 2 private AnyType getSubstituteFromRightChild(AnyType x, AvlNode<AnyType> parent, 3 AvlNode<AnyType> t) { 4 5 AnyType temp = null; 6 7 if( t.left != null ){ 8 9 return getSubstituteFromRightChild( x, t, t.left ); 10 11 }else{ 12 13 temp = t.element; 14 15 if( x.compareTo( parent.element ) == 0 ){ //如果该替代节点是被删除节点的儿子(第一代子孙),使用右孩子替代父亲的右孩子 16 17 parent.right = t.right; 18 19 }else{ //如果不是第一代子孙,则使用右孩子替代父亲节点的左孩子 20 21 parent.left = t.right; 22 } 23 24 parent.height = Math.max( height( parent.left ), height( parent.right ) ) + 1; 25 26 parent = keepBalanceIfNot( parent ); 27 28 return temp; 29 } 30 }
保持平衡的方法
1 /**
2 *
3 * @description 保持树的平衡,自己总结的规律,尚未经过大量测试
4 * @author luwei
5 * @date 2012-12-3 下午3:26:22
6 * @param p
7 * @return
8 */
9 private AvlNode<AnyType> keepBalanceIfNot(AvlNode<AnyType> p) {
10
11 if( height( p.left ) - height( p.right ) == 2 ){ //左高右低
12
13 //左子树的左节点比左子树的右节点高,则使用左旋转
14 //左子树的左节点比左子树的右节点底,则使用左-右双旋转
15 //经过列举,得出结论:只要判断左孩子的右节点是否为空,为空,并且左子树比右子树高2,则是第一种情况,使用左旋转
16 if( p.right == null ){
17
18 p = rotateWithLeftChild( p );
19
20 }else if( p.right != null ){ //左孩子的右节点不为空,则必定是左节点的右子树比左节点的左子树高,则是第二种情况
21
22 p = doubleWithLeftChild( p );
23 }
24 }else if( height( p.right ) - height( p.left ) == 2 ){
25
26 //该情况与上一种情况互为镜像
27 if( p.left == null ){
28
29 p = rotateWithRightChild( p );
30
31 }else if( p.left != null ){
32
33 p = doubleWithRightChild( p );
34 }
35 }
36
37 return p;
38 }
这是我自己做的一个java的实现,只经过简单测试,如果网友发现了有任何的问题,请给我留言,我会及时更正,谢谢!
下面把原书上附带的代码也贴上:
1 // AvlTree class 2 // 3 // CONSTRUCTION: with no initializer 4 // 5 // ******************PUBLIC OPERATIONS********************* 6 // void insert( x ) --> Insert x 7 // void remove( x ) --> Remove x (unimplemented) 8 // boolean contains( x ) --> Return true if x is present 9 // Comparable findMin( ) --> Return smallest item 10 // Comparable findMax( ) --> Return largest item 11 // boolean isEmpty( ) --> Return true if empty; else false 12 // void makeEmpty( ) --> Remove all items 13 // void printTree( ) --> Print tree in sorted order 14 // ******************ERRORS******************************** 15 // Throws UnderflowException as appropriate 16 17 /** 18 * Implements an AVL tree. 19 * Note that all "matching" is based on the compareTo method. 20 * @author Mark Allen Weiss 21 */ 22 public class AvlTree<AnyType extends Comparable<? super AnyType>> 23 { 24 /** 25 * Construct the tree. 26 */ 27 public AvlTree( ) 28 { 29 root = null; 30 } 31 32 /** 33 * Insert into the tree; duplicates are ignored. 34 * @param x the item to insert. 35 */ 36 public void insert( AnyType x ) 37 { 38 root = insert( x, root ); 39 } 40 41 /** 42 * Remove from the tree. Nothing is done if x is not found. 43 * @param x the item to remove. 44 */ 45 public void remove( AnyType x ) 46 { 47 System.out.println( "Sorry, remove unimplemented" ); 48 } 49 50 /** 51 * Find the smallest item in the tree. 52 * @return smallest item or null if empty. 53 */ 54 public AnyType findMin( ) 55 { 56 if( isEmpty( ) ) 57 throw new UnderflowException( ); 58 return findMin( root ).element; 59 } 60 61 /** 62 * Find the largest item in the tree. 63 * @return the largest item of null if empty. 64 */ 65 public AnyType findMax( ) 66 { 67 if( isEmpty( ) ) 68 throw new UnderflowException( ); 69 return findMax( root ).element; 70 } 71 72 /** 73 * Find an item in the tree. 74 * @param x the item to search for. 75 * @return true if x is found. 76 */ 77 public boolean contains( AnyType x ) 78 { 79 return contains( x, root ); 80 } 81 82 /** 83 * Make the tree logically empty. 84 */ 85 public void makeEmpty( ) 86 { 87 root = null; 88 } 89 90 /** 91 * Test if the tree is logically empty. 92 * @return true if empty, false otherwise. 93 */ 94 public boolean isEmpty( ) 95 { 96 return root == null; 97 } 98 99 /** 100 * Print the tree contents in sorted order. 101 */ 102 public void printTree( ) 103 { 104 if( isEmpty( ) ) 105 System.out.println( "Empty tree" ); 106 else 107 printTree( root ); 108 } 109 110 /** 111 * Internal method to insert into a subtree. 112 * @param x the item to insert. 113 * @param t the node that roots the subtree. 114 * @return the new root of the subtree. 115 */ 116 private AvlNode<AnyType> insert( AnyType x, AvlNode<AnyType> t ) 117 { 118 if( t == null ) 119 return new AvlNode<AnyType>( x, null, null ); 120 121 int compareResult = x.compareTo( t.element ); 122 123 if( compareResult < 0 ) 124 { 125 t.left = insert( x, t.left ); 126 if( height( t.left ) - height( t.right ) == 2 ) 127 if( x.compareTo( t.left.element ) < 0 ) 128 t = rotateWithLeftChild( t ); 129 else 130 t = doubleWithLeftChild( t ); 131 } 132 else if( compareResult > 0 ) 133 { 134 t.right = insert( x, t.right ); 135 if( height( t.right ) - height( t.left ) == 2 ) 136 if( x.compareTo( t.right.element ) > 0 ) 137 t = rotateWithRightChild( t ); 138 else 139 t = doubleWithRightChild( t ); 140 } 141 else 142 ; // Duplicate; do nothing 143 t.height = Math.max( height( t.left ), height( t.right ) ) + 1; 144 return t; 145 } 146 147 /** 148 * Internal method to find the smallest item in a subtree. 149 * @param t the node that roots the tree. 150 * @return node containing the smallest item. 151 */ 152 private AvlNode<AnyType> findMin( AvlNode<AnyType> t ) 153 { 154 if( t == null ) 155 return t; 156 157 while( t.left != null ) 158 t = t.left; 159 return t; 160 } 161 162 /** 163 * Internal method to find the largest item in a subtree. 164 * @param t the node that roots the tree. 165 * @return node containing the largest item. 166 */ 167 private AvlNode<AnyType> findMax( AvlNode<AnyType> t ) 168 { 169 if( t == null ) 170 return t; 171 172 while( t.right != null ) 173 t = t.right; 174 return t; 175 } 176 177 /** 178 * Internal method to find an item in a subtree. 179 * @param x is item to search for. 180 * @param t the node that roots the tree. 181 * @return true if x is found in subtree. 182 */ 183 private boolean contains( AnyType x, AvlNode<AnyType> t ) 184 { 185 while( t != null ) 186 { 187 int compareResult = x.compareTo( t.element ); 188 189 if( compareResult < 0 ) 190 t = t.left; 191 else if( compareResult > 0 ) 192 t = t.right; 193 else 194 return true; // Match 195 } 196 197 return false; // No match 198 } 199 200 /** 201 * Internal method to print a subtree in sorted order. 202 * @param t the node that roots the tree. 203 */ 204 private void printTree( AvlNode<AnyType> t ) 205 { 206 if( t != null ) 207 { 208 printTree( t.left ); 209 System.out.println( t.element ); 210 printTree( t.right ); 211 } 212 } 213 214 /** 215 * Return the height of node t, or -1, if null. 216 */ 217 private int height( AvlNode<AnyType> t ) 218 { 219 return t == null ? -1 : t.height; 220 } 221 222 /** 223 * Rotate binary tree node with left child. 224 * For AVL trees, this is a single rotation for case 1. 225 * Update heights, then return new root. 226 */ 227 private AvlNode<AnyType> rotateWithLeftChild( AvlNode<AnyType> k2 ) 228 { 229 AvlNode<AnyType> k1 = k2.left; 230 k2.left = k1.right; 231 k1.right = k2; 232 k2.height = Math.max( height( k2.left ), height( k2.right ) ) + 1; 233 k1.height = Math.max( height( k1.left ), k2.height ) + 1; 234 return k1; 235 } 236 237 /** 238 * Rotate binary tree node with right child. 239 * For AVL trees, this is a single rotation for case 4. 240 * Update heights, then return new root. 241 */ 242 private AvlNode<AnyType> rotateWithRightChild( AvlNode<AnyType> k1 ) 243 { 244 AvlNode<AnyType> k2 = k1.right; 245 k1.right = k2.left; 246 k2.left = k1; 247 k1.height = Math.max( height( k1.left ), height( k1.right ) ) + 1; 248 k2.height = Math.max( height( k2.right ), k1.height ) + 1; 249 return k2; 250 } 251 252 /** 253 * Double rotate binary tree node: first left child 254 * with its right child; then node k3 with new left child. 255 * For AVL trees, this is a double rotation for case 2. 256 * Update heights, then return new root. 257 */ 258 private AvlNode<AnyType> doubleWithLeftChild( AvlNode<AnyType> k3 ) 259 { 260 k3.left = rotateWithRightChild( k3.left ); 261 return rotateWithLeftChild( k3 ); 262 } 263 264 /** 265 * Double rotate binary tree node: first right child 266 * with its left child; then node k1 with new right child. 267 * For AVL trees, this is a double rotation for case 3. 268 * Update heights, then return new root. 269 */ 270 private AvlNode<AnyType> doubleWithRightChild( AvlNode<AnyType> k1 ) 271 { 272 k1.right = rotateWithLeftChild( k1.right ); 273 return rotateWithRightChild( k1 ); 274 } 275 276 private static class AvlNode<AnyType> 277 { 278 // Constructors 279 AvlNode( AnyType theElement ) 280 { 281 this( theElement, null, null ); 282 } 283 284 AvlNode( AnyType theElement, AvlNode<AnyType> lt, AvlNode<AnyType> rt ) 285 { 286 element = theElement; 287 left = lt; 288 right = rt; 289 height = 0; 290 } 291 292 AnyType element; // The data in the node 293 AvlNode<AnyType> left; // Left child 294 AvlNode<AnyType> right; // Right child 295 int height; // Height 296 } 297 298 /** The tree root. */ 299 private AvlNode<AnyType> root; 300 301 302 // Test program 303 public static void main( String [ ] args ) 304 { 305 AvlTree<Integer> t = new AvlTree<Integer>( ); 306 final int NUMS = 4000; 307 final int GAP = 37; 308 309 System.out.println( "Checking... (no more output means success)" ); 310 311 for( int i = GAP; i != 0; i = ( i + GAP ) % NUMS ) 312 t.insert( i ); 313 314 if( NUMS < 40 ) 315 t.printTree( ); 316 if( t.findMin( ) != 1 || t.findMax( ) != NUMS - 1 ) 317 System.out.println( "FindMin or FindMax error!" ); 318 319 for( int i = 1; i < NUMS; i++ ) 320 if( !t.contains( i ) ) 321 System.out.println( "Find error1!" ); 322 } 323 }
这是我自己做的一个java的实现,只经过简单测试,如果网友发现了有任何的问题,请给我留言,我会及时更正,谢谢!
转载请声明出处!
浙公网安备 33010602011771号