平衡二叉树(AVL树)构造、添加/删除元素(Java)

发布于:2023-01-09 ⋅ 阅读:(259) ⋅ 点赞:(0)

一、AVL树的定义

在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了它。
——百度百科

二、AVL树添加、删除元素

说明:

AVLTress: 外部类
root: AVL树的根节点
nodeStack: 存放某次删除元素时遍历过的节点栈
Node: 内部类,节点类型
this:当前节点
left:当前节点的左子节点
right:当前节点的右子节点
value: 当前节点存放的值
height(): 当前节点高度
rightHeight:右子树高度
leftHeight():左子树高度
leftRoate():令当前节点左旋转
rightRotate():令当前节点右旋转
balance(): 调整以当前节点为根节点的子树平衡

1.添加

往AVL树 this 添加新节点 node 的思想是使用递归 :
外部类函数 add(E value) , 由根节点调用 root.add( new Node( value ) )
内部类函数 add(Node node),由当前节点调用

步骤:

  1. node.value <= this.value ,将其添加到当前树的左子树,
    this.left !=null 则 this.left.add( node )
    this.left ==null , this.left = node
  2. nodevalue > this.value , 将其添加到当前树的右子树
    this.right != null 则 this.right.add( node )
    this.right == null , this.right = node

在每次寻找适当的插入位置时,当前节点 this 遍历过的节点为所有可能发生失衡的节点,因此需要在插入节点完成后对当前节点进行平衡调整:this.balance()。

添加元素 代码:

	    /**
	     * Desc:    添加新元素 value
	     *
	     * @author pikachu
	     */
	    public void add(E value) {
	        if (root == null) {
	            root = new Node(value);
	
	        } else {
	            root.add(new Node(value));
	        }
	    }
        /**
         * Desc:    将 node加入到当前节点 this
         *
         * @param node 待加入节点
         * @author pikachu
         */
        private void add(Node node) {
            if (node.value.compareTo(this.value) <= 0) {
                if (this.left != null) {
                    this.left.add(node);
                } else {
                    this.left = node;
                }
            } else {
                if (this.right != null) {
                    this.right.add(node);
                } else {
                    this.right = node;
                }
            }
            balance();
        }

2.旋转

往AVL树中添加元素时可能会破环树的平衡,处理形况分为4种:LL、RR、LR、RL。
对树进行旋转时总是以高度较大的子树为圆心旋转,从而降低其高度,
左旋转:逆时针旋转
右旋转:顺时针旋转

LL

LL型:破环平衡的节点在左子树的左子节点,leftHeight>rightHeight && left.leftHeight >= left.rightHeight,4(this)绕2(left)右转(顺时针)

RR

RR型:破环平衡的节点在右子树的右子节点,rightHeight>leftHeight && right.rightHeight >= right.leftHeight,6(this)绕8(right)左转(逆时针)

LR

LR型:破环平衡的节点在左子树的右子节点,leftHeight > rightHeight &&
left.rightHeight > left.leftHeight。

  1. 2(left)绕3(left.right)左转(逆时针)
  2. 5(this)绕3(left)右旋转(顺时针)

RL

RL型:破环平衡的节点在右子树的左子节点, rightHeight > leftHeight &&
right.leftHeight > right.rightHeight。

  1. 5(right)绕4(right.left)右转(顺时针)
  2. 2(this)绕4(right)左旋转(逆时针)

调节平衡代码

        /**
         * Desc:调节当前节点的子树平衡
         */
        private void balance() {
            // 平衡调整
            if (rightHeight() - leftHeight() > 1) {
                if (right.rightHeight() >= right.leftHeight()) {
                    //RR型
                    leftRotate();
                } else {
                    //RL型
                    right.rightRotate();
                    leftRotate();
                }
                return;
            }

            if (leftHeight() - rightHeight() > 1) {
                if (left.leftHeight() >= left.rightHeight()) {
                    //LL型
                    rightRotate();
                } else {
                    //LR型
                    left.leftRotate();
                    rightRotate();
                }
            }
        }

3.删除

remove( Node parent, Node delNode ) : 移除preNode的子树中的node节点(如果存在), 当待删除的节点有左右子树时,将右子树的最小节点作为 parent的新子节点, 删除后将遍历路径上的节点nodesStack平衡。

说明:
this 当前节点
parent 当前节点的父节点
delNode 待删除节点
rightMinNode 扫描 this 右子树最小节点(this.right != null 时实际删除的节点)
preNode rightMinNode的父节点,初始为this

寻找待删除节点时的路径可能不只是每次遍历的当前节点 this ,还有 preNode 遍历过的节点。如以下情况:

此时 preNNode 遍历过的节点 17 失衡,因此每次当 preNode 发生移动时也需要将 preNode 压入 nodeStack。

步骤:

  1. delNode < this,往左子树查找删除,若左子树为空则结束
  2. delNode > this,往右子树查找删除,若右子树为空则结束
  3. delNode = this 找到待删除的节点 this
    3.1 this.right不为空,在 this.right的子树中寻找最小节点 rightMinNode,作为新子树的根节点
    3.1.1preNode和rightMinNode发生移动,待删除节点的右子节点有左子树, 并在该树(根节点 rightMinNode,left = null, right 不一定为 null)中找到最小值。
    3.1.2 preNode和rightMinNode没有移动,待删除节点的右子节点无左子树,this.right.left = null,
    右子树的最小节点就是this.right,令this拼接上this.right.right。
    3.1.3 将 this.value 替换为 rightMinNode.value
    3.2 this.right为空
    3.2.1 this =parent.right),parent.right = this.left;
    this = parent.left, parent.left = this.left;
    3.2.2 this = parent ,删除树中根节点,替换根节点 root = root.left;

删除元素时也可能破环树的平衡,需要记录删除时遍历的节点 nodeStack,然后将其逆序取每个遍历过的节点 node 进行平衡化 : node.balance();

删除元素代码

	    /**
	     * Desc:
	     *
	     * @param value 待移除的元素
	     * @author pikachu
	     */
	    public void remove(E value) {
	        if (root == null) {
	            System.out.println("Tree is empty");
	        } else {
	            root.remove(root, new Node(value));
	            // 本次删除结束,将nodes清空
	            nodeStack.clear();
	        }
	    }
    
        /**
         * Desc:移除preNode的子树中的node节点(如果存在),
         * 当待删除的节点有左右子树时,将右子树的最小节点作为 parent的新子节点,
         * 删除后将遍历路径上的节点nodesStack平衡
         *
         * @param parent  当前节点的父节点
         * @param delNode 待删除节点
         * @author pikachu
         */

        private void remove(Node parent, Node delNode) {
            nodeStack.push(this);
            //  1.delNode < this,往左子树查找删除;若左子树为空则结束
            if (delNode.value.compareTo(this.value) < 0) {
                if (this.left != null) {
                    this.left.remove(this, delNode);
                }
            }
            //  2.delNode > this,往右子树查找删除;若右子树为空则结束
            else if (delNode.value.compareTo(this.value) > 0) {
                if (this.right != null) {
                    this.right.remove(this, delNode);
                }
            }
            //  3.delNode = this 找到待删除的节点 this
            else if (delNode.value.compareTo(this.value) == 0) {
                Node preNode = this;
                //  3.1 this.right不为空,在 this.right的子树中寻找最小节点 rightMinNode,作为新子树的根节点
                if (this.right != null) {
                    Node rightMinNode = preNode.right;
                    while (rightMinNode.left != null) {
                        preNode = rightMinNode;
                        rightMinNode = rightMinNode.left;
                        //  !!!添加遍历路径
                        nodeStack.add(preNode);
                    }
                    if (preNode.right != rightMinNode) {
                        //  3.1.1 preNode和rightMinNode发生移动,待删除节点的右子节点有左子树,
                        //  并在该树(根节点 rightMinNode,left = null, right 不一定为 null)中找到最小值
                        preNode.left = rightMinNode.right;
                    } else {
                        //  3.1.2 preNode和rightMinNode没有移动,待删除节点的右子节点无左子树,this.right.left = null,
                        //  右子树的最小节点就是this.right,令this拼接上this.right.right
                        preNode.right = preNode.right.right;
                    }
                    //  3.1.3 将 this.value 替换为 rightMinNode.value
                    this.value = rightMinNode.value;

                }
                // 3.2 this.right为空
                else {
                    if (this == parent.right) {
                        parent.right = this.left;
                    } else if (this == parent.left) {
                        parent.left = this.left;
                    }
                    // 3.2.1 删除树中根节点
                    else {
                        root = root.left;
                    }
                }

                // 逆序平衡遍历过的节点
                while (nodeStack.size() > 0) {
                    nodeStack.pop().balance();
                }
            }
        }
    

三、完整代码

Test 为测试类


package;


import java.util.*;

class TestLL {
    public static void main(String[] args) {
        Integer[] a = new Integer[]{6, 5, 8, 7, 9, 10};
        AVLTree<Integer> avlTree = new AVLTree<>(a);
        System.out.println(avlTree.height());
        System.out.println("avlTree = " + avlTree);
        avlTree.infixOrder();

        System.out.println("avlTree.testBalance() = " + avlTree.testBalance());
    }
}

class TestRR {
    public static void main(String[] args) {
        Integer[] a = new Integer[]{2, 1, 3, 5, 6, 4};
        AVLTree<Integer> avlTree = new AVLTree<>(a);

        System.out.println(avlTree.height());
        System.out.println("avlTree = " + avlTree);
        avlTree.infixOrder();
        System.out.println("avlTree.testBalance() = " + avlTree.testBalance());
    }
}

class TestLR {
    public static void main(String[] args) {
        Integer[] a = new Integer[]{5, 2, 6, 1, 3, 4};
        AVLTree<Integer> avlTree = new AVLTree<>(a);

        System.out.println("avlTree.height() = " + avlTree.height());
        System.out.println("avlTree = " + avlTree);
        avlTree.infixOrder();
        System.out.println("avlTree.testBalance() = " + avlTree.testBalance());
    }
}

class TestRL {
    public static void main(String[] args) {
        Integer[] a = new Integer[]{2, 1, 5, 4, 6, 3};
        AVLTree<Integer> avlTree = new AVLTree<>(a);

        System.out.println("avlTree.height() = " + avlTree.height());
        System.out.println("avlTree = " + avlTree);
        avlTree.infixOrder();

        avlTree.infixOrder();
        System.out.println("avlTree.testBalance() = " + avlTree.testBalance());
    }
}

class RandomTest {
    public static void main(String[] args) {
        int n = 40;
        Random random = new Random();
        Integer[] a = new Integer[n];
        for (int i = 0; i < a.length; i++) {
            a[i] = random.nextInt(n);
        }

        System.out.println(n + "个随机数:");


        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ,");
            if (i % 20 == 0) {
                System.out.println();
            }
        }
        AVLTree<Integer> avlTree = new AVLTree<>();
        // 测试创建
        System.out.println("\n- - - - - - - - 测试随机数创建AVL树 - - - - - - - -");
        for (Integer integer : a) {
            avlTree.add(integer);
//            System.out.println("avlTree.testBalance() = " + avlTree.testBalance());
        }
        System.out.println("- - - - - - - - - - - 创建完成 - - - - - - - - - -");
        System.out.println("avlTree = " + avlTree);
        avlTree.infixOrder();
        System.out.println("avlTree.height() = " + avlTree.height());

        // 测试删除
        System.out.println("- - - - - - - - 测试AVL树中删除随机数 - - - - - - - -");
        for (int i = 0; i < a.length; i++) {
            Integer data = a[random.nextInt(a.length)];
            System.out.print(" remove = " + data);
            avlTree.remove(data);
//            System.out.println("avlTree.testBalance() = " + avlTree.testBalance());
            avlTree.testBalance();
            if (i % 10 == 0) {
                System.out.println();
            }
        }
        System.out.println();
        System.out.println("avlTree.testBalance() = " + avlTree.testBalance());
        System.out.println("- - - - - - - - - - 删除完成 - - - - - - - - - -");
        avlTree.infixOrder();
    }
}

class TestRemove {
    public static void main(String[] args) {
        Integer[] a = new Integer[]{11, 14, 17, 6, 11, 19, 9, 13, 7, 15, 13, 8, 7, 19, 9, 12, 2, 4, 7, 19};
        int n = 100;
        Random random = new Random();
//        Integer[] a = new Integer[n];
//        for (int i = 0; i < a.length; i++) {
//            a[i] = random.nextInt(n);
//        }
        AVLTree<Integer> avlTree = new AVLTree<>(a);
        System.out.println(n + "个随机数:");
        System.out.println("- - - - - - 创建 - - - - - ");
        System.out.println("avlTree.height() = " + avlTree.height());
        System.out.println("avlTree = " + avlTree);
        avlTree.infixOrder();
        System.out.println("avlTree.testBalance() = " + avlTree.testBalance());

        System.out.println("- - - - - - 删除 - - - - - ");
        for (Integer integer : a) {
            avlTree.remove(integer);
            avlTree.infixOrder();
            System.out.println("avlTree.testBalance() = " + avlTree.testBalance());
        }
        System.out.println("avlTree = " + avlTree);

    }
}

/**
 * Desc: 平衡二叉树
 *
 * @author pikachu
 * @date: 2022/8/20 9:54
 */

public class AVLTree<E extends Comparable<E>> {

    private Node root;
    private Stack<Node> nodeStack = new Stack<>();  // 删除过程中遍历的节点
    
 	public AVLTree() {
    }
    
    public AVLTree(E[] value) {
        for (E item : value) {
            add(item);
        }
    }

    /**
     * Desc:    添加新元素 value
     *
     * @author pikachu
     */
    public void add(E value) {
        if (root == null) {
            root = new Node(value);

        } else {
            root.add(new Node(value));
        }
    }


    /**
     * Desc:
     *
     * @param value 待移除的元素
     * @author pikachu
     */
    public void remove(E value) {
        if (root == null) {
            System.out.println("Tree is empty");
        } else {
            root.remove(root, new Node(value));
            // 本次删除结束,将nodes清空
            nodeStack.clear();
        }
    }

    /**
     * Desc:    中序遍历该子树(从小到大输出)
     *
     * @author pikachu
     */
    public void infixOrder() {
        if (root == null) {
            System.out.println("Tree is empty");
        } else {
            root.infixOrder();
            System.out.println();
        }
    }

    /**
     * Desc:测试当前树是否满足AVL树
     *
     * @return 树是否平衡
     */
    public boolean testBalance() {
        if (root != null) {
            return root.testBalance();
        }
        return false;
    }

    /**
     * Desc:获取树的高度
     *
     * @return {@link int}
     */
    public int height() {
        System.out.println("leftHeight() = " + root.leftHeight() + " & rightHeight() = " + root.rightHeight());
        return root.height();
    }

    @Override
    public String toString() {
        return root == null ? "Tree is empty" : "root : " + root.value;
    }

    private class Node {
        private Node left;
        private Node right;
        private E value;

        private Node(E value) {
            this.value = value;
        }

        /**
         * Desc:    将 node加入到当前节点 this
         *
         * @param node 待加入节点
         * @author pikachu
         */
        private void add(Node node) {
            if (node.value.compareTo(this.value) <= 0) {
                if (this.left != null) {
                    this.left.add(node);
                } else {
                    this.left = node;
                }
            } else {
                if (this.right != null) {
                    this.right.add(node);
                } else {
                    this.right = node;
                }
            }
            balance();
        }

        /**
         * Desc:移除preNode的子树中的node节点(如果存在),
         * 当待删除的节点有左右子树时,将右子树的最小节点作为 parent的新子节点,
         * 删除后将遍历路径上的节点nodesStack平衡
         *
         * @param parent  当前节点的父节点
         * @param delNode 待删除节点
         * @author pikachu
         */

        private void remove(Node parent, Node delNode) {
            nodeStack.push(this);
            //  1.delNode < this,往左子树查找删除;若左子树为空则结束
            if (delNode.value.compareTo(this.value) < 0) {
                if (this.left != null) {
                    this.left.remove(this, delNode);
                }
            }
            //  2.delNode > this,往右子树查找删除;若右子树为空则结束
            else if (delNode.value.compareTo(this.value) > 0) {
                if (this.right != null) {
                    this.right.remove(this, delNode);
                }
            }
            //  3.delNode = this 找到待删除的节点 this
            else if (delNode.value.compareTo(this.value) == 0) {
                Node preNode = this;
                //  3.1 this.right不为空,在 this.right的子树中寻找最小节点 rightMinNode,作为新子树的根节点
                if (this.right != null) {
                    Node rightMinNode = preNode.right;
                    while (rightMinNode.left != null) {
                        preNode = rightMinNode;
                        rightMinNode = rightMinNode.left;
                        //  !!!添加遍历路径
                        nodeStack.add(preNode);
                    }
                    if (preNode.right != rightMinNode) {
                        //  3.1.1 preNode和rightMinNode发生移动,待删除节点的右子节点有左子树,
                        //  并在该树(根节点 rightMinNode,left = null, right 不一定为 null)中找到最小值
                        preNode.left = rightMinNode.right;
                    } else {
                        //  3.1.2 preNode和rightMinNode没有移动,待删除节点的右子节点无左子树,this.right.left = null,
                        //  右子树的最小节点就是this.right,令this拼接上this.right.right
                        preNode.right = preNode.right.right;
                    }
                    //  3.1.3 将 this.value 替换为 rightMinNode.value
                    this.value = rightMinNode.value;

                }
                // 3.2 this.right为空
                else {
                    if (this == parent.right) {
                        parent.right = this.left;
                    } else if (this == parent.left) {
                        parent.left = this.left;
                    }
                    // 3.2.1 删除树中根节点
                    else {
                        root = root.left;
                    }
                }

                // 逆序平衡遍历过的节点
                while (nodeStack.size() > 0) {
                    nodeStack.pop().balance();
                }
            }
        }

        private int leftHeight() {
            return left != null ? left.height() : 0;
        }

        private int rightHeight() {
            return right != null ? right.height() : 0;
        }

        private int height() {
            return Math.max(left != null ? left.height() : 0, right != null ? right.height() : 0) + 1;
        }

        /**
         * Desc:当前节点 this绕 right左旋转
         */
        private void leftRotate() {
            Node newNode = new Node(this.value);
            newNode.left = left;
            newNode.right = right.left;
            value = right.value;
            right = right.right;
            left = newNode;
        }

        /**
         * Desc:当前节点 this绕 left右旋转
         */
        private void rightRotate() {
            Node newNode = new Node(value);
            newNode.left = left.right;
            newNode.right = right;
            left.right = newNode;
            value = left.value;
            left = left.left;
            right = newNode;
        }

        /**
         * Desc:调节当前节点的子树平衡
         */
        private void balance() {
            // 平衡调整
            if (rightHeight() - leftHeight() > 1) {
                if (right.rightHeight() >= right.leftHeight()) {
                    //RR型
                    leftRotate();
                } else {
                    //RL型
                    right.rightRotate();
                    leftRotate();
                }
                return;
            }

            if (leftHeight() - rightHeight() > 1) {
                if (left.leftHeight() >= left.rightHeight()) {
                    //LL型
                    rightRotate();
                } else {
                    //LR型
                    left.leftRotate();
                    rightRotate();
                }
            }
        }

        /**
         * Desc:
         * 中序遍历二叉排序树后刚好从小到大输出
         *
         * @author pikachu
         */
        private void infixOrder() {
            if (left != null) {
                left.infixOrder();
            }
            System.out.print(value + " ,");
            if (right != null) {
                right.infixOrder();
            }
        }

        /**
         * Desc:测试当前子树是否平衡
         *
         * @return {@link boolean}  平衡:true 不平衡:false
         */
        private boolean testBalance() {
            boolean flag = true;
            if (flag && left != null) {
                flag = left.testBalance();
            }
            if (flag && Math.abs(leftHeight() - rightHeight()) > 1) {
                System.out.println("AVL 树失衡!");
//                System.out.println("失衡节点:" + value + " & leftHeight=" + leftHeight() + " & rightHeight=" + rightHeight());
                flag = false;
                throw new RuntimeException("失衡节点:" + value + " & leftHeight=" + leftHeight() + " & rightHeight=" + rightHeight());
            }
            if (flag && right != null) {
                flag = right.testBalance();
            }
            return flag;
        }

        @Override
        public String toString() {
            return this.value.toString();
        }

    }
}






四、测试

TestRandom:随机数创建AVL树,测试添加/元素后树的平衡

  1. 只在添加时调整平衡

  1. 删除元素后也调整平衡

  1. 增加数据,不逐个输出树是否平衡,不平衡时将抛出异常


网站公告

今日签到

点亮在社区的每一天
去签到