平衡二叉树(AVL树)
平衡二叉树(AVL树)
平衡二叉树简介:
平衡树(Balance Tree,BT) 指的是,任意节点的子树的高度差都小于等于1。常见的符合平衡树的有,B树(多路平衡搜索树)、AVL树(二叉平衡搜索树)等。
具有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1, 并且左右两个子树都是-棵平衡二叉树。
平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。可以保证查询效率高。
举例看下下面AVL树的特点吧:左右两个子树的高度差的绝对值不超过1
第三棵树的左子树高度是3,右子树高度是1,3-1=2,所以第三个不是AVL树
AVL树左旋
AVL树左旋图解
要求: 给你一个数列,创建出对应的平衡二叉树.数列 {4,3,6,5,7,8}
AVL树左旋步骤:
- 创建一个新的节点,值为当前节点的值
- 把新节点的的左子树设置为原节点的左子树:4-->指向3
- 把新节点的右子树设置为当前节点的右子树的左子树
- 把当前节点的值:4 换成当前右子节点的值:6
- 把当前节点的右子树设为右子树的右子树
- 把当前节点的左子树设为新的节点:6-->指向4
AVL树的高度计算
核心:首先要把一棵树的高度以及左子树和右子树的高度计算出来
Node类中添加这三个方法:

1 /** 2 * 返回以当前节点为根节点的树的高度 3 * 4 * @return 返回树的高度 5 */ 6 public int heightTree() { 7 // 比较左子树跟右子树的高度,返回最大的。+1是因为树本身还要站一层 8 return Math.max(left == null ? 0 : left.heightTree(), right == null ? 0 : right.heightTree()) + 1; 9 } 10 11 /** 12 * 返回左子树的高度 13 * 14 * @return 左子树高度 15 */ 16 public int leftHeight() { 17 if (left == null) { 18 return 0; 19 } 20 return left.heightTree(); 21 } 22 23 /** 24 * 返回右子树的高度 25 * 26 * @return 右子树高度 27 */ 28 public int rightHeight() { 29 if (right == null) { 30 return 0; 31 } 32 return right.heightTree(); 33 }
AVLTree类:直接用上个二叉排序树的树代码即可!

1 class AVLTree { 2 private Node root; 3 4 public Node getRoot() { 5 return root; 6 } 7 8 /** 9 * 查找要删除的节点 10 * 11 * @param value 12 * @return 13 */ 14 public Node delSearch(int value) { 15 if (root == null) { 16 return null; 17 } else { 18 return root.delSearch(value); 19 } 20 } 21 22 /** 23 * 查找到要删除节点的父节点 24 * 25 * @param value 26 * @return 27 */ 28 public Node delSearchParent(int value) { 29 if (root == null) { 30 return null; 31 } else { 32 return root.delSearchParent(value); 33 } 34 } 35 36 /** 37 * 找到最小值并删除 38 * 39 * @param node 40 * @return 返回删除节点的值 41 */ 42 public int delRightTreeMin(Node node) { 43 // 作一个辅助节点 44 Node target = node; 45 // 循环往左子树进行查找,就会找到最小值 46 while (target.left != null) { 47 target = target.left; 48 } 49 // 删除最小值 50 delNode(target.value); 51 // 返回最小值 52 return target.value; 53 } 54 55 /** 56 * 删除节点 57 * 58 * @param value 59 */ 60 public void delNode(int value) { 61 if (root == null) { 62 return; 63 } else { 64 // 1、找到要删除的节点 65 Node targetNode = delSearch(value); 66 // 没有找到 67 if (targetNode == null) { 68 return; 69 } 70 // 表示这颗二叉排序树只有一个节点(父节点) 71 if (root.left == null && root.right == null) { 72 root = null; 73 return; 74 } 75 76 // 2、找到要删除节点的父节点 77 Node parentNode = delSearchParent(value); 78 // 表示要删除的节点是一个叶子节点 79 if (targetNode.left == null && targetNode.right == null) { 80 // 继续判断这个叶子节点是父节点的左子节点还是右子节点 81 if (parentNode.left != null && parentNode.left.value == value) { 82 // 将这个叶子节点置为空 83 parentNode.left = null; 84 } else if (parentNode.right != null && parentNode.right.value == value) { 85 parentNode.right = null; 86 } 87 } else if (targetNode.left != null && targetNode.right != null) {// 删除有两颗子树的节点 88 // 找到最小值 89 int minVal = delRightTreeMin(targetNode.right); 90 // 重置 91 targetNode.value = minVal; 92 } else {// 删除只有一颗子树的节点 93 if (targetNode.left != null) {// 如果要删除的节点有左子节点 94 if (parentNode != null) { 95 // 待删除节点是父节点的左子节点 96 if (parentNode.left.value == value) { 97 parentNode.left = targetNode.left; 98 } else {// 待删除节点是父节点的右子节点 99 parentNode.right = targetNode.left; 100 } 101 } else { 102 root = targetNode.left; 103 } 104 } else {// 如果要删除的节点有右子节点 105 if (parentNode != null) { 106 // 待删除节点是父节点的左子节点 107 if (parentNode.left.value == value) { 108 parentNode.left = targetNode.right; 109 } else {// 待删除节点是父节点的右子节点 110 parentNode.right = targetNode.right; 111 } 112 } else { 113 root = targetNode.right; 114 } 115 } 116 } 117 } 118 } 119 120 /** 121 * 添加节点的方法 122 * 123 * @param node 124 */ 125 public void addNode(Node node) { 126 // root节点为空,就让root成为根节点 127 if (root == null) { 128 root = node; 129 } else {// root节点不为空,就继续向树中添加节点 130 root.addNode(node); 131 } 132 } 133 134 /** 135 * 进行中序遍历 136 */ 137 public void infixOrder() { 138 if (root != null) { 139 root.infixOrder(); 140 } else { 141 System.out.println("二叉树为空,无法进行排序!"); 142 } 143 } 144 }
测试树的高度:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public static void main(String[] args) { int [] arr = {4, 3, 6, 5, 7, 8}; // 创建AVL树对象 AVLTree avlTree = new AVLTree(); for ( int i = 0; i < arr.length; i++) { // 添加节点 avlTree.addNode( new Node(arr[i])); } // 中序遍历 System. out .println( "======================中序遍历======================" ); avlTree.infixOrder(); System. out .println( "======================没有平衡处理前======================" ); System. out .println( "没有平衡处理前树的高度:" + avlTree.getRoot().heightTree()); System. out .println( "没有平衡处理前树的左子树高度:" + avlTree.getRoot().leftHeight()); System. out .println( "没有平衡处理前树的右子树高度:" + avlTree.getRoot().rightHeight()); } |
附上总体代码实现:

1 package Demo11_平衡二叉树_AVL树; 2 3 /** 4 * @author zhangzhixi 5 * @date 2021/3/12 22:58 6 */ 7 public class AVLTreeDemo { 8 public static void main(String[] args) { 9 int[] arr = {4, 3, 6, 5, 7, 8}; 10 11 // 创建AVL树对象 12 AVLTree avlTree = new AVLTree(); 13 14 for (int i = 0; i < arr.length; i++) { 15 // 添加节点 16 avlTree.addNode(new Node(arr[i])); 17 } 18 19 // 中序遍历 20 System.out.println("======================中序遍历======================"); 21 avlTree.infixOrder(); 22 System.out.println("======================没有平衡处理前======================"); 23 System.out.println("没有平衡处理前树的高度:" + avlTree.getRoot().heightTree()); 24 System.out.println("没有平衡处理前树的左子树高度:" + avlTree.getRoot().leftHeight()); 25 System.out.println("没有平衡处理前树的右子树高度:" + avlTree.getRoot().rightHeight()); 26 } 27 } 28 29 class AVLTree { 30 private Node root; 31 32 public Node getRoot() { 33 return root; 34 } 35 36 /** 37 * 查找要删除的节点 38 * 39 * @param value 40 * @return 41 */ 42 public Node delSearch(int value) { 43 if (root == null) { 44 return null; 45 } else { 46 return root.delSearch(value); 47 } 48 } 49 50 /** 51 * 查找到要删除节点的父节点 52 * 53 * @param value 54 * @return 55 */ 56 public Node delSearchParent(int value) { 57 if (root == null) { 58 return null; 59 } else { 60 return root.delSearchParent(value); 61 } 62 } 63 64 /** 65 * 找到最小值并删除 66 * 67 * @param node 68 * @return 返回删除节点的值 69 */ 70 public int delRightTreeMin(Node node) { 71 // 作一个辅助节点 72 Node target = node; 73 // 循环往左子树进行查找,就会找到最小值 74 while (target.left != null) { 75 target = target.left; 76 } 77 // 删除最小值 78 delNode(target.value); 79 // 返回最小值 80 return target.value; 81 } 82 83 /** 84 * 删除节点 85 * 86 * @param value 87 */ 88 public void delNode(int value) { 89 if (root == null) { 90 return; 91 } else { 92 // 1、找到要删除的节点 93 Node targetNode = delSearch(value); 94 // 没有找到 95 if (targetNode == null) { 96 return; 97 } 98 // 表示这颗二叉排序树只有一个节点(父节点) 99 if (root.left == null && root.right == null) { 100 root = null; 101 return; 102 } 103 104 // 2、找到要删除节点的父节点 105 Node parentNode = delSearchParent(value); 106 // 表示要删除的节点是一个叶子节点 107 if (targetNode.left == null && targetNode.right == null) { 108 // 继续判断这个叶子节点是父节点的左子节点还是右子节点 109 if (parentNode.left != null && parentNode.left.value == value) { 110 // 将这个叶子节点置为空 111 parentNode.left = null; 112 } else if (parentNode.right != null && parentNode.right.value == value) { 113 parentNode.right = null; 114 } 115 } else if (targetNode.left != null && targetNode.right != null) {// 删除有两颗子树的节点 116 // 找到最小值 117 int minVal = delRightTreeMin(targetNode.right); 118 // 重置 119 targetNode.value = minVal; 120 } else {// 删除只有一颗子树的节点 121 if (targetNode.left != null) {// 如果要删除的节点有左子节点 122 if (parentNode != null) { 123 // 待删除节点是父节点的左子节点 124 if (parentNode.left.value == value) { 125 parentNode.left = targetNode.left; 126 } else {// 待删除节点是父节点的右子节点 127 parentNode.right = targetNode.left; 128 } 129 } else { 130 root = targetNode.left; 131 } 132 } else {// 如果要删除的节点有右子节点 133 if (parentNode != null) { 134 // 待删除节点是父节点的左子节点 135 if (parentNode.left.value == value) { 136 parentNode.left = targetNode.right; 137 } else {// 待删除节点是父节点的右子节点 138 parentNode.right = targetNode.right; 139 } 140 } else { 141 root = targetNode.right; 142 } 143 } 144 } 145 } 146 } 147 148 /** 149 * 添加节点的方法 150 * 151 * @param node 152 */ 153 public void addNode(Node node) { 154 // root节点为空,就让root成为根节点 155 if (root == null) { 156 root = node; 157 } else {// root节点不为空,就继续向树中添加节点 158 root.addNode(node); 159 } 160 } 161 162 /** 163 * 进行中序遍历 164 */ 165 public void infixOrder() { 166 if (root != null) { 167 root.infixOrder(); 168 } else { 169 System.out.println("二叉树为空,无法进行排序!"); 170 } 171 } 172 } 173 174 class Node { 175 int value; 176 Node left; 177 Node right; 178 179 public Node(int value) { 180 this.value = value; 181 } 182 183 @Override 184 public String toString() { 185 return "Node{" + 186 "value=" + value + 187 '}'; 188 } 189 190 /** 191 * 返回以当前节点为根节点的树的高度 192 * 193 * @return 返回树的高度 194 */ 195 public int heightTree() { 196 // 比较左子树跟右子树的高度,返回最大的。+1是因为树本身还要站一层 197 return Math.max(left == null ? 0 : left.heightTree(), right == null ? 0 : right.heightTree()) + 1; 198 } 199 200 /** 201 * 返回左子树的高度 202 * 203 * @return 左子树高度 204 */ 205 public int leftHeight() { 206 if (left == null) { 207 return 0; 208 } 209 return left.heightTree(); 210 } 211 212 /** 213 * 返回右子树的高度 214 * 215 * @return 右子树高度 216 */ 217 public int rightHeight() { 218 if (right == null) { 219 return 0; 220 } 221 return right.heightTree(); 222 } 223 224 /** 225 * 查找到要删除的节点 226 * 227 * @param value 希望删除节点的值 228 * @return 找到了就返回这个要删除的节点,没有找到就返回null 229 */ 230 public Node delSearch(int value) { 231 // 找到的就是要删除的节点 232 if (value == this.value) { 233 return this; 234 } else if (value < this.value) { 235 /**向左子节点查找*/ 236 if (this.left == null) { 237 return null; 238 } 239 // 继续递归查找 240 return this.left.delSearch(value); 241 } else {// 要删除节点的值是大于等于当前节点的值 242 if (this.right == null) { 243 return null; 244 } 245 return this.right.delSearch(value); 246 } 247 } 248 249 /** 250 * 查找到要删除节点的父节点 251 * 252 * @param value 要删除的节点的值 253 * @return 找到就返回要删除节点的父节点,没有找到就返回null 254 */ 255 public Node delSearchParent(int value) { 256 // 如果当前节点就是要删除的节点的父节点,就返回 257 if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) { 258 return this; 259 } else { 260 // 满足条件表示向左递归查找 261 if (this.left != null && value < this.value) { 262 // 向左子树递归找到就返回 263 return this.left.delSearchParent(value); 264 } else if (this.right != null && value >= this.value) { 265 // 向右子树递归找到就返回 266 return this.right.delSearchParent(value); 267 } else { 268 // 没有找到要删除节点的父节点 269 return null; 270 } 271 } 272 } 273 274 /** 275 * 二叉排序树的添加 276 * 277 * @param node 278 */ 279 public void addNode(Node node) { 280 if (node == null) { 281 return; 282 } 283 // 判断传进来的节点值与当前节点的关系 284 if (node.value < this.value) { 285 // 左子节点为null 286 if (this.left == null) { 287 this.left = node; 288 } else { 289 // 递归的向左子树添加节点 290 this.left.addNode(node); 291 } 292 } else { // 传入的节点大于等于当前节点 293 if (this.right == null) { 294 this.right = node; 295 } else { 296 // 递归的向右子树添加节点 297 this.right.addNode(node); 298 } 299 } 300 301 } 302 303 /** 304 * 二叉排序树的中序遍历 305 */ 306 void infixOrder() { 307 if (this.left != null) { 308 this.left.infixOrder(); 309 } 310 System.out.println(this); 311 if (this.right != null) { 312 this.right.infixOrder(); 313 } 314 } 315 }
AVL左旋代码:
在Node节点中添加AVL树左旋的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /** * AVL树左旋转的方法 */ public void leftHand() { // 1.创建新的节点,值为当前节点的值 Node newNode = new Node( this .value); // 2.把新节点的左子树设置为原节点的左子树 newNode.left = this .left; // 3.把新节点的右子树设为当前节点的右子树的左子树 newNode.right = this .right.left; // 4.把当前节点的值换成当前节点右子节点的值 this .value = this .right.value; // 5.把当前节点的右子树设为右子树的右子树 this .right = this .right.right; // 6.把当前节点的左子树设置为新的节点 this .left = newNode; } |
在Node节点中添加树的方法中加入左旋判断:
1 /** 2 * 二叉排序树的添加 3 * 4 * @param node 5 */ 6 public void addNode(Node node) { 7 if (node == null) { 8 return; 9 } 10 // 判断传进来的节点值与当前节点的关系 11 if (node.value < this.value) { 12 // 左子节点为null 13 if (this.left == null) { 14 this.left = node; 15 } else { 16 // 递归的向左子树添加节点 17 this.left.addNode(node); 18 } 19 } else { // 传入的节点大于等于当前节点 20 if (this.right == null) { 21 this.right = node; 22 } else { 23 // 递归的向右子树添加节点 24 this.right.addNode(node); 25 } 26 } 27 // 当添加完成一个节点后:(右子树的高度-左子树的高度)>1,进行左旋操作 28 if (rightHeight() - leftHeight() > 1) { 29 // 进行左旋操作 30 leftHand(); 31 } 32 }
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public static void main(String[] args) { int [] arr = {4, 3, 6, 5, 7, 8}; // 创建AVL树对象 AVLTree avlTree = new AVLTree(); for ( int i = 0; i < arr.length; i++) { // 添加节点 avlTree.addNode( new Node(arr[i])); } // 中序遍历 System. out .println( "======================中序遍历======================" ); avlTree.infixOrder(); System. out .println( "======================AVL树左旋平衡处理======================" ); System. out .println( "左旋平衡处理后树的高度:" + avlTree.getRoot().heightTree()); System. out .println( "左旋平衡处理后树的左子树高度:" + avlTree.getRoot().leftHeight()); System. out .println( "左旋平衡处理后树的右子树高度:" + avlTree.getRoot().rightHeight()); } |
AVL树右旋
AVL树右旋图解
AVL树右旋代码:
在Node类中添加平衡二叉树右旋代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /** * AVL树右旋转的方法 */ public void rightHand() { // 1.创建新的节点,值为当前节点的值 Node newNode = new Node( this .value); // 2.把新节点的右子树设置为当前节点的右子树 newNode.right = this .right; // 3.把新节点的左子树设为当前节点的左子树的右子树 newNode.left = this .left.right; // 4.把当前节点的值换成当前节点左子节点的值 this .value = this .left.value; // 5.把当前节点的左子树设为左子树的左子树 this .left = this .left.left; // 6.把当前节点的右子树设置为新的节点 this .right = newNode; } |
在Node类中添加节点时候增加判断判断节点应该左旋还是右旋:
1 /** 2 * 二叉排序树的添加 3 * 4 * @param node 5 */ 6 public void addNode(Node node) { 7 if (node == null) { 8 return; 9 } 10 // 判断传进来的节点值与当前节点的关系 11 if (node.value < this.value) { 12 // 左子节点为null 13 if (this.left == null) { 14 this.left = node; 15 } else { 16 // 递归的向左子树添加节点 17 this.left.addNode(node); 18 } 19 } else { // 传入的节点大于等于当前节点 20 if (this.right == null) { 21 this.right = node; 22 } else { 23 // 递归的向右子树添加节点 24 this.right.addNode(node); 25 } 26 } 27 // 当添加完成一个节点后:(右子树的高度-左子树的高度)>1,进行左旋操作 28 if (rightHeight() - leftHeight() > 1) { 29 // 进行左旋操作 30 leftHand(); 31 } 32 // 当添加完成一个节点后:(左子树的高度-右子树的高度)>1,进行右旋操作 33 if (leftHeight() - rightHeight() > 1) { 34 // 进行左旋操作 35 rightHand(); 36 } 37 }
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public static void main(String[] args) { // 测试左旋转 //int[] arr = {4, 3, 6, 5, 7, 8}; // 测试右旋转 int [] arr = {10, 12, 8, 9, 7, 6}; // 创建AVL树对象 AVLTree avlTree = new AVLTree(); for ( int i = 0; i < arr.length; i++) { // 添加节点 avlTree.addNode( new Node(arr[i])); } // 中序遍历 /*System.out.println("======================中序遍历======================"); avlTree.infixOrder(); System.out.println("======================AVL树左旋平衡处理======================"); System.out.println("左旋平衡处理后树的高度:" + avlTree.getRoot().heightTree()); System.out.println("左旋平衡处理后树的左子树高度:" + avlTree.getRoot().leftHeight()); System.out.println("左旋平衡处理后树的右子树高度:" + avlTree.getRoot().rightHeight());*/ System. out .println( "======================中序遍历======================" ); avlTree.infixOrder(); System. out .println( "======================AVL树右旋平衡处理======================" ); System. out .println( "右旋平衡处理后树的高度:" + avlTree.getRoot().heightTree()); System. out .println( "右旋平衡处理后树的左子树高度:" + avlTree.getRoot().leftHeight()); System. out .println( "右旋平衡处理后树的右子树高度:" + avlTree.getRoot().rightHeight()); System. out .println( "右旋平衡处理后树的根节点的值:" +avlTree.getRoot().value); } |
AVL树双旋
AVL树双旋问题以及图解:
右旋遇到的问题:
前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换。
比如数列int[] arr = { 10, 11, 7, 6, 8, 9 }; 运行原来的代码可以看到,并没有转成 AVL 树.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public static void main(String[] args) { // 测试左旋转 //int[] arr = {4, 3, 6, 5, 7, 8}; // 测试右旋转 //int[] arr = {10, 12, 8, 9, 7, 6}; // 测试旋转 int [] arr = { 10, 11, 7, 6, 8, 9 }; // 创建AVL树对象 AVLTree avlTree = new AVLTree(); for ( int i = 0; i < arr.length; i++) { // 添加节点 avlTree.addNode( new Node(arr[i])); } // 中序遍历 /*System.out.println("======================中序遍历======================"); avlTree.infixOrder(); System.out.println("======================AVL树左旋平衡处理======================"); System.out.println("左旋平衡处理后树的高度:" + avlTree.getRoot().heightTree()); System.out.println("左旋平衡处理后树的左子树高度:" + avlTree.getRoot().leftHeight()); System.out.println("左旋平衡处理后树的右子树高度:" + avlTree.getRoot().rightHeight());*/ System. out .println( "======================中序遍历======================" ); avlTree.infixOrder(); System. out .println( "======================AVL树右旋平衡处理======================" ); System. out .println( "右旋平衡处理后树的高度:" + avlTree.getRoot().heightTree()); System. out .println( "右旋平衡处理后树的左子树高度:" + avlTree.getRoot().leftHeight()); System. out .println( "右旋平衡处理后树的右子树高度:" + avlTree.getRoot().rightHeight()); System. out .println( "右旋平衡处理后树的根节点的值:" + avlTree.getRoot().value); } |
可以发现,将数列进行二叉排序树后的右旋处理,并没有变成AVL数:(3 - 1) > 1 所以说不满足AVL树
解决思路:
- 当符号右旋转的条件时
- 如果它的左子树的右子树高度大于它的左子树的高度
- 先对当前这个结点的左节点进行左旋转
- 在对当前结点进行右旋转的操作即可
代码实现:
在添加节点的时候增加判断,直接上代码了:
/** * 二叉排序树的添加 * * @param node */ public void addNode(Node node) { if (node == null) { return; } // 判断传进来的节点值与当前节点的关系 if (node.value < this.value) { // 左子节点为null if (this.left == null) { this.left = node; } else { // 递归的向左子树添加节点 this.left.addNode(node); } } else { // 传入的节点大于等于当前节点 if (this.right == null) { this.right = node; } else { // 递归的向右子树添加节点 this.right.addNode(node); } } // 当添加完成一个节点后:(右子树的高度-左子树的高度)>1,进行左旋操作 if (rightHeight() - leftHeight() > 1) { // 左旋之双旋判断 if (this.right != null && this.right.rightHeight() > this.right.rightHeight()) { // 先对右子结点进行右旋转 this.right.rightHand(); // 再进行左旋操作 leftHand(); } else { // 直接进行左旋操作 leftHand(); } // 右旋判断: 当添加完成一个节点后:(左子树的高度-右子树的高度)>1,进行右旋操作 } else if (leftHeight() - rightHeight() > 1) { // 右旋之双旋判断 if (this.left != null && this.left.rightHeight() > this.left.leftHeight()) { // 先对当前结点的左结点(左子树)->左旋转 this.left.leftHand(); // 再进行右旋操作 rightHand(); } else { // 直接进行右旋操作 rightHand(); } } }
测试:
总体代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 | package Demo11_平衡二叉树_AVL树; /** * @author zhangzhixi * @date 2021/3/12 22:58 */ public class AVLTreeDemo { public static void main(String[] args) { // 测试左旋转 //int[] arr = {4, 3, 6, 5, 7, 8}; // 测试右旋转 //int[] arr = {10, 12, 8, 9, 7, 6}; // 测试旋转 int [] arr = {10, 11, 7, 6, 8, 9}; // 创建AVL树对象 AVLTree avlTree = new AVLTree(); for ( int i = 0; i < arr.length; i++) { // 添加节点 avlTree.addNode( new Node(arr[i])); } // 中序遍历 /*System.out.println("======================中序遍历======================"); avlTree.infixOrder(); System.out.println("======================AVL树左旋平衡处理======================"); System.out.println("左旋平衡处理后树的高度:" + avlTree.getRoot().heightTree()); System.out.println("左旋平衡处理后树的左子树高度:" + avlTree.getRoot().leftHeight()); System.out.println("左旋平衡处理后树的右子树高度:" + avlTree.getRoot().rightHeight());*/ System. out .println( "======================中序遍历======================" ); avlTree.infixOrder(); System. out .println( "======================AVL树右旋平衡处理======================" ); System. out .println( "右旋平衡处理后树的高度:" + avlTree.getRoot().heightTree()); System. out .println( "右旋平衡处理后树的左子树高度:" + avlTree.getRoot().leftHeight()); System. out .println( "右旋平衡处理后树的右子树高度:" + avlTree.getRoot().rightHeight()); System. out .println( "右旋平衡处理后树的根节点的值:" + avlTree.getRoot().value); } } class AVLTree { private Node root; public Node getRoot() { return root; } /** * 查找要删除的节点 * * @param value * @return */ public Node delSearch( int value) { if (root == null ) { return null ; } else { return root.delSearch(value); } } /** * 查找到要删除节点的父节点 * * @param value * @return */ public Node delSearchParent( int value) { if (root == null ) { return null ; } else { return root.delSearchParent(value); } } /** * 找到最小值并删除 * * @param node * @return 返回删除节点的值 */ public int delRightTreeMin(Node node) { // 作一个辅助节点 Node target = node; // 循环往左子树进行查找,就会找到最小值 while (target.left != null ) { target = target.left; } // 删除最小值 delNode(target.value); // 返回最小值 return target.value; } /** * 删除节点 * * @param value */ public void delNode( int value) { if (root == null ) { return ; } else { // 1、找到要删除的节点 Node targetNode = delSearch(value); // 没有找到 if (targetNode == null ) { return ; } // 表示这颗二叉排序树只有一个节点(父节点) if (root.left == null && root.right == null ) { root = null ; return ; } // 2、找到要删除节点的父节点 Node parentNode = delSearchParent(value); // 表示要删除的节点是一个叶子节点 if (targetNode.left == null && targetNode.right == null ) { // 继续判断这个叶子节点是父节点的左子节点还是右子节点 if (parentNode.left != null && parentNode.left.value == value) { // 将这个叶子节点置为空 parentNode.left = null ; } else if (parentNode.right != null && parentNode.right.value == value) { parentNode.right = null ; } } else if (targetNode.left != null && targetNode.right != null ) { // 删除有两颗子树的节点 // 找到最小值 int minVal = delRightTreeMin(targetNode.right); // 重置 targetNode.value = minVal; } else { // 删除只有一颗子树的节点 if (targetNode.left != null ) { // 如果要删除的节点有左子节点 if (parentNode != null ) { // 待删除节点是父节点的左子节点 if (parentNode.left.value == value) { parentNode.left = targetNode.left; } else { // 待删除节点是父节点的右子节点 parentNode.right = targetNode.left; } } else { root = targetNode.left; } } else { // 如果要删除的节点有右子节点 if (parentNode != null ) { // 待删除节点是父节点的左子节点 if (parentNode.left.value == value) { parentNode.left = targetNode.right; } else { // 待删除节点是父节点的右子节点 parentNode.right = targetNode.right; } } else { root = targetNode.right; } } } } } /** * 添加节点的方法 * * @param node */ public void addNode(Node node) { // root节点为空,就让root成为根节点 if (root == null ) { root = node; } else { // root节点不为空,就继续向树中添加节点 root.addNode(node); } } /** * 进行中序遍历 */ public void infixOrder() { if (root != null ) { root.infixOrder(); } else { System. out .println( "二叉树为空,无法进行排序!" ); } } } class Node { int value; Node left; Node right; public Node( int value) { this .value = value; } @Override public String toString() { return "Node{" + "value=" + value + '}' ; } /** * 返回以当前节点为根节点的树的高度 * * @return 返回树的高度 */ public int heightTree() { // 比较左子树跟右子树的高度,返回最大的。+1是因为树本身还要站一层 return Math.max(left == null ? 0 : left.heightTree(), right == null ? 0 : right.heightTree()) + 1; } /** * 返回左子树的高度 * * @return 左子树高度 */ public int leftHeight() { if (left == null ) { return 0; } return left.heightTree(); } /** * 返回右子树的高度 * * @return 右子树高度 */ public int rightHeight() { if (right == null ) { return 0; } return right.heightTree(); } /** * 查找到要删除的节点 * * @param value 希望删除节点的值 * @return 找到了就返回这个要删除的节点,没有找到就返回null */ public Node delSearch( int value) { // 找到的就是要删除的节点 if (value == this .value) { return this ; } else if (value < this .value) { /**向左子节点查找*/ if ( this .left == null ) { return null ; } // 继续递归查找 return this .left.delSearch(value); } else { // 要删除节点的值是大于等于当前节点的值 if ( this .right == null ) { return null ; } return this .right.delSearch(value); } } /** * 查找到要删除节点的父节点 * * @param value 要删除的节点的值 * @return 找到就返回要删除节点的父节点,没有找到就返回null */ public Node delSearchParent( int value) { // 如果当前节点就是要删除的节点的父节点,就返回 if (( this .left != null && this .left.value == value) || ( this .right != null && this .right.value == value)) { return this ; } else { // 满足条件表示向左递归查找 if ( this .left != null && value < this .value) { // 向左子树递归找到就返回 return this .left.delSearchParent(value); } else if ( this .right != null && value >= this .value) { // 向右子树递归找到就返回 return this .right.delSearchParent(value); } else { // 没有找到要删除节点的父节点 return null ; } } } /** * 二叉排序树的添加 * * @param node */ public void addNode(Node node) { if (node == null ) { return ; } // 判断传进来的节点值与当前节点的关系 if (node.value < this .value) { // 左子节点为null if ( this .left == null ) { this .left = node; } else { // 递归的向左子树添加节点 this .left.addNode(node); } } else { // 传入的节点大于等于当前节点 if ( this .right == null ) { this .right = node; } else { // 递归的向右子树添加节点 this .right.addNode(node); } } // 当添加完成一个节点后:(右子树的高度-左子树的高度)>1,进行左旋操作 if (rightHeight() - leftHeight() > 1) { // 左旋之双旋判断 if ( this .right != null && this .right.rightHeight() > this .right.rightHeight()) { // 先对右子结点进行右旋转 this .right.rightHand(); // 再进行左旋操作 leftHand(); } else { // 直接进行左旋操作 leftHand(); } // 右旋判断: 当添加完成一个节点后:(左子树的高度-右子树的高度)>1,进行右旋操作 } else if (leftHeight() - rightHeight() > 1) { // 右旋之双旋判断 if ( this .left != null && this .left.rightHeight() > this .left.leftHeight()) { // 先对当前结点的左结点(左子树)->左旋转 this .left.leftHand(); // 再进行右旋操作 rightHand(); } else { // 直接进行右旋操作 rightHand(); } } } /** * 二叉排序树的中序遍历 */ void infixOrder() { if ( this .left != null ) { this .left.infixOrder(); } System. out .println( this ); if ( this .right != null ) { this .right.infixOrder(); } } /** * AVL树左旋转的方法 */ public void leftHand() { // 1.创建新的节点,值为当前节点的值 Node newNode = new Node( this .value); // 2.把新节点的左子树设置为当前节点的左子树 newNode.left = this .left; // 3.把新节点的右子树设为当前节点的右子树的左子树 newNode.right = this .right.left; // 4.把当前节点的值换成当前节点右子节点的值 this .value = this .right.value; // 5.把当前节点的右子树设为右子树的右子树 this .right = this .right.right; // 6.把当前节点的左子树设置为新的节点 this .left = newNode; } /** * AVL树右旋转的方法 */ public void rightHand() { // 1.创建新的节点,值为当前节点的值 Node newNode = new Node( this .value); // 2.把新节点的右子树设置为当前节点的右子树 newNode.right = this .right; // 3.把新节点的左子树设为当前节点的左子树的右子树 newNode.left = this .left.right; // 4.把当前节点的值换成当前节点左子节点的值 this .value = this .left.value; // 5.把当前节点的左子树设为左子树的左子树 this .left = this .left.left; // 6.把当前节点的右子树设置为新的节点 this .right = newNode; } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话