平衡二叉树(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树左旋步骤:

  1. 创建一个新的节点,值为当前节点的值
  2. 把新节点的的左子树设置为原节点的左子树:4-->指向3
  3. 把新节点的右子树设置为当前节点的右子树的左子树
  4. 把当前节点的值:4 换成当前右子节点的值:6
  5. 把当前节点的右子树设为右子树的右子树
  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     }
View Code
复制代码

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 }
View Code
复制代码

 测试树的高度:

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 }
View Code
复制代码

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树   

解决思路:

  1. 当符号右旋转的条件时
  2. 如果它的左子树的右子树高度大于它的左子树的高度
  3. 先对当前这个结点的左节点进行左旋转
  4. 在对当前结点进行右旋转的操作即可

 代码实现:

在添加节点的时候增加判断,直接上代码了:

复制代码
/**
 * 二叉排序树的添加
 *
 * @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;
    }
}

 

posted @   Java小白的搬砖路  阅读(276)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话

喜欢请打赏

扫描二维码打赏

支付宝打赏

点击右上角即可分享
微信分享提示