【数据结构与算法】【15】AVL树

什么是AVL树

AVL树是一种高度平衡的二叉查找树,AVL是人名的缩写

AVL树在二叉查找树的基础上,增加了一条约束条件

任意节点的左右子树高度差不大于1

为什么要设计AVL树

AVL树的设计,是为了弥补普通二叉查找树的一些缺陷

比如我们将一堆已经按从大到小排序好的数据插入二叉树,这些数据最终会全部摆放二叉树的左子树上面

此时我们去查找二叉树中的某个元素,其查找效率和遍历数组的效率基本一样,二叉树已经完全丧失了二分查找的优势

AVL树就是要保证二叉树的绝对平衡,这样其查找效率就可以永远保持在O(log2n)

AVL树的优点在于,查询效率很高,但要维护其平衡性,在插入时需要进行一些转换,这样就会导致其插入效率比较低

因此,AVL树适合查询频繁的场景,但不适合频繁插入的场景

平衡因子

左右子树的高度差叫做平衡因子,英文术语Balance Factor,简称BF

二叉树旋转

在这里插入图片描述
如上图所示,以40为中心,分别将50向左旋转,将30向右旋转

仍然可以保持二叉树的大小特征,但是平衡因子却变了

利用这个特点,我们可以将不平衡的二叉树,旋转为平衡的二叉树

主要分为四种方式:左单旋,右单旋,左右双旋,右左双旋

上面的图示忽略了子节点,当被旋转的节点还有子节点时,整个旋转过程可以用下面的动画来表示

在这里插入图片描述

在这里插入图片描述

左单旋

图片较小可以右键拖拽到新页面单独查看

场景:右子树较高,新节点被插到右子树右侧

在这里插入图片描述
右单旋

场景:左子树较高,新节点被插到左子树左侧

在这里插入图片描述
左右双旋

场景:左子树较高,新节点被插到左子树右侧
在这里插入图片描述
右左双旋

场景:右子树较高,新节点被插到右子树左侧
在这里插入图片描述
更新平衡因子

当我们向AVL树中插入一个新节点时,假设新插入的节点叫做current,其父节点叫做parent

parent平衡因子的变化可分为两种情况

  1. 本来就有left,插入了right,parent.bf由±1变为0,子树高度不变
  2. 本来没有left和right,插入了left或right,parent.bf由0变为±1,子树高度发生变化

parent的平衡因子更新很简单,插入右侧就减1,插入左侧就加1

对于第一种情况,由于parent子树的总高度不变,因此不需要再更新parent.parent的平衡因子

对于第二种情况,则需要向上递归,更新parent的parent的平衡因子,直到某个parent的平衡因子为0

递归过程中可能还会遇到另外一种情况,就是平衡因子由±1变为±2

此时AVL树的平衡已经被打破了,需要以此节点为中心,对AVL树进行旋转

从AVL树中删除一个元素

AVL树的删除和插入基本类似,由于AVL树本来就是高度平衡的,删除一个元素只会影响到parent节点的平衡因子

判断删除后,parent节点的平衡因子是否大于1,如果大于1,按照一样的方法进行旋转即可

普通二叉树转AVL树

这其实是一个伪命题,因为它的实现方法太简单了

将普通二叉树元素按任意方式全部遍历出来,再新建一个AVL树,将元素全部插入即可

代码实现

这里主要实现插入功能,删除和插入思路基本一致,其它二叉树通用功能,就不再逐个累述了


	//AVL节点
	@SuppressWarnings("all")
	public class AVLNode<T extends Comparable> {
	
	    //节点值
	    T value;
	
	    //平衡因子
	    int bf;
	
	    //子节点
	    AVLNode<T> left;
	    AVLNode<T> right;
	
	    //父节点
	    AVLNode<T> parent;
	
	    public int getChildCount() {
	        if (left == null && right == null)
	            return 0;
	        if (left != null && right != null)
	            return 2;
	        return 1;
	    }
	
	    @Override
	    public String toString() {
	        return String.valueOf(value + "(" + bf + ")");
	    }
	}


	//AVL树
	@SuppressWarnings("all")
	public class AVLTree<T extends Comparable> {
	
	    AVLNode<T> root;
	
	    //插入数据
	    public void Insert(T value) {
	        //树为空,直接创建根节点
	        if (root == null) {
	            AVLNode node = new AVLNode();
	            node.value = value;
	            root = node;
	            return;
	        }
	        //递归遍历所有节点,寻找插入位置
	        Insert(root, value);
	    }
	
	    //递归遍历所有节点,寻找插入位置
	    protected void Insert(AVLNode<T> node, T value) {
	        //值等于当前节点,已存在,直接结束
	        if (value.compareTo(node.value) == 0)
	            return;
	        //值小于当前节点,插入到左子树
	        if (value.compareTo(node.value) < 0) {
	            if (node.left == null) {
	                AVLNode<T> newNode = new AVLNode();
	                newNode.value = value;
	                newNode.parent = node;
	                newNode.bf = 0;
	                node.left = newNode;
	                //递归更新平衡因子
	                UpdateBalanceFactor(node, newNode);
	                return;
	            }
	            Insert(node.left, value);
	        }
	        //值大于当前节点,插入到右子树
	        if (value.compareTo(node.value) > 0) {
	            if (node.right == null) {
	                AVLNode<T> newNode = new AVLNode();
	                newNode.value = value;
	                newNode.parent = node;
	                newNode.bf = 0;
	                node.right = newNode;
	                //递归更新平衡因子
	                UpdateBalanceFactor(node, newNode);
	                return;
	            }
	            Insert(node.right, value);
	        }
	    }
	
	    //递归更新平衡因子
	    protected void UpdateBalanceFactor(AVLNode<T> parent, AVLNode<T> current) {
	        if (parent == null)
	            return;
	        //更新parent的平衡因子
	        if (parent.left == current)
	            parent.bf = parent.bf + 1;
	        if (parent.right == current)
	            parent.bf = parent.bf - 1;
	        //parent子树高度不变,不需要向上递归更新
	        if (parent.bf == 0)
	            return;
	        //parent子树高度改变,但仍然保持平衡,向上递归更新
	        if (parent.bf == -1 || parent.bf == 1) {
	            UpdateBalanceFactor(parent.parent, parent);
	            return;
	        }
	        //parent子树高度改变,且打破平衡,即平衡因子由±1变为±2
	        //以parent为中心,对AVL树进行旋转
	        Rotate(parent);
	    }
	
	    //旋转AVL树
	    protected void Rotate(AVLNode<T> parent) {
	        //右子树较高,新节点被插到右子树右侧,左单旋
	        if (parent.bf == -2 && parent.right.bf == -1) {
	            RotateL(parent);
	            return;
	        }
	        //左子树较高,新节点被插到左子树左侧,右单旋
	        if (parent.bf == 2 && parent.left.bf == 1) {
	            RotateR(parent);
	            return;
	        }
	        //左子树较高,新节点被插到左子树右侧,左右双旋
	        if (parent.bf == 2 && parent.left.bf == -1) {
	            RotateLR(parent);
	            return;
	        }
	        //右子树较高,新节点被插到右子树左侧,右左双旋
	        if (parent.bf == -2 && parent.right.bf == 1) {
	            RotateRL(parent);
	            return;
	        }
	    }
	
	    //左单旋
	    protected void RotateL(AVLNode<T> parent) {
	        //原parent的右孩子,旋转后作为新的parent
	        AVLNode<T> node1 = parent.right;
	        //node1的左孩子,失去parent后,移动到原parent的右节点上,可能为空
	        AVLNode<T> node2 = parent.right.left;
	        //原parent的parent,旋转后作为新parent的parent,可能为空
	        AVLNode<T> node3 = parent.parent;
	        //更新节点上下级关系
	        //更新node1和node3的关系
	        node1.parent = node3;
	        if (node3 != null) {
	            if (node3.left == parent)
	                node3.left = node1;
	            if (node3.right == parent)
	                node3.right = node1;
	        }
	        //更新node1和parent的关系
	        node1.left = parent;
	        parent.parent = node1;
	        //更新node2和parent的关系
	        parent.right = node2;
	        if (node2 != null)
	            node2.parent = parent;
	        if (node2 != null)
	            parent.bf = 0;
	        else
	            parent.bf = 1;
	        //更新AVL树的根节点
	        if (node3 == null) {
	            root = node1;
	            node1.parent = null;
	        }
	        //更新所有发生变化的平衡因子
	        node1.bf = 0;
	        if (node3 != null)
	            node3.bf = node3.bf - 1;
	    }
	
	    //右单旋
	    protected void RotateR(AVLNode<T> parent) {
	        //原parent的左孩子,旋转后作为新的parent
	        AVLNode<T> node1 = parent.left;
	        //node1的右孩子,位置被占据后,移动到原parent的左节点上,可能为空
	        AVLNode<T> node2 = parent.left.right;
	        //原parent的parent,旋转后作为新parent的parent,可能为空
	        AVLNode<T> node3 = parent.parent;
	        //更新节点上下级关系
	        //更新node1和node3的关系
	        node1.parent = node3;
	        if (node3 != null) {
	            if (node3.left == parent)
	                node3.left = node1;
	            if (node3.right == parent)
	                node3.right = node1;
	        }
	        //更新node1和parent的关系
	        node1.right = parent;
	        parent.parent = node1;
	        //更新node2和parent的关系
	        parent.left = node2;
	        if (node2 != null)
	            node2.parent = parent;
	        if (node2 != null)
	            parent.bf = 0;
	        else
	            parent.bf = -1;
	        //更新AVL树的根节点
	        if (node3 == null) {
	            root = node1;
	            node1.parent = null;
	        }
	        //更新所有发生变化的平衡因子
	        node1.bf = 0;
	        if (node3 != null)
	            node3.bf = node3.bf + 1;
	    }
	
	    //左右双旋
	    protected void RotateLR(AVLNode<T> parent) {
	        RotateL(parent.left);
	        RotateR(parent);
	    }
	
	    //右左双旋
	    protected void RotateRL(AVLNode<T> parent) {
	        RotateR(parent.right);
	        RotateL(parent);
	    }
	}

结果验证

现在我们以上面四种情况为例,将AVL树打印出来,验证下我们的代码是否正确


	import java.util.LinkedHashMap;
	import java.util.LinkedList;
	import java.util.List;
	import java.util.Map;
	
	//AVL树打印工具
	@SuppressWarnings("all")
	public class AVLPrinter {
	
	    AVLNode root;
	
	    //中序遍历和广度遍历结果
	    final List<AVLNode> inorderTraverseList = new LinkedList();
	    final List<AVLNode> breadthTraverseList = new LinkedList();
	
	    //所有节点的坐标
	    final Map<AVLNode, Integer> xMap = new LinkedHashMap();
	    final Map<AVLNode, Integer> yMap = new LinkedHashMap();
	
	    //整个输出矩阵中的所有字符
	    final List<List<Character>> charForm = new LinkedList();
	
	    //下个节点的坐标
	    int nextX;
	    int nextY;
	
	    public void setRootNode(AVLNode root) {
	        this.root = root;
	    }
	
	    public void Print() {
	        //清空旧数据
	        inorderTraverseList.clear();
	        breadthTraverseList.clear();
	        xMap.clear();
	        yMap.clear();
	        charForm.clear();
	        nextX = 0;
	        nextY = 0;
	        //如果节点为空,直接结束
	        if (root == null) {
	            System.out.println("Empty Tree");
	            return;
	        }
	        //中序遍历,计算出每个节点的横向X坐标
	        InorderTraverse(root);
	        //层次遍历,计算出每个节点的纵向Y坐标
	        List<AVLNode> levelNodeList = new LinkedList();
	        levelNodeList.add(root);
	        BreadthTraverse(levelNodeList);
	        //生成最终的二维字符数组
	        FillCharForm();
	        //打印结果
	        for (List<Character> row : charForm) {
	            for (Character cell : row)
	                System.out.print(cell);
	            System.out.println();
	        }
	    }
	
	    //中序遍历
	    private void InorderTraverse(AVLNode node) {
	        if (node.left != null)
	            InorderTraverse(node.left);
	        inorderTraverseList.add(node);
	        xMap.put(node, nextX);
	        nextX = nextX + node.toString().length() + 1;
	        if (node.right != null)
	            InorderTraverse(node.right);
	    }
	
	    //层次遍历,当前层次的所有节点
	    private void BreadthTraverse(List<AVLNode> levelNodeList) {
	        if (levelNodeList.isEmpty())
	            return;
	        for (AVLNode node : levelNodeList)
	            yMap.put(node, nextY);
	        breadthTraverseList.addAll(levelNodeList);
	        nextY = nextY + 1;
	        List<AVLNode> nextLevelNodeList = new LinkedList();
	        for (AVLNode node : levelNodeList) {
	            if (node.left != null)
	                nextLevelNodeList.add(node.left);
	            if (node.right != null)
	                nextLevelNodeList.add(node.right);
	        }
	        BreadthTraverse(nextLevelNodeList);
	    }
	
	    //生成最终的二维字符数组
	    private void FillCharForm() {
	        //填充占位符
	        for (int y = 0; y <= nextY - 1; y++) {
	            List<Character> row = new LinkedList();
	            charForm.add(row);
	            for (int x = 0; x <= nextX - 2; x++) {
	                char character = toFullWidth(' ');
	                row.add(character);
	            }
	        }
	        //填充节点字符串
	        for (AVLNode node : breadthTraverseList) {
	            int x = xMap.get(node);
	            int y = yMap.get(node);
	            String value = node.toString();node.toString();
	            for (int i = 0; i < value.length(); i++)
	                setChar(x + i, y, value.charAt(i));
	        }
	        //填充连接符
	        for (AVLNode node : breadthTraverseList) {
	            if (node.left != null) {
	                int x1 = xMap.get(node.left);
	                int x2 = xMap.get(node);
	                int y = yMap.get(node);
	                setChar(x1, y, '|');
	                for (int i = x1 + 1; i < x2; i++)
	                    setChar(i, y, '-');
	            }
	            if (node.right != null) {
	                int x1 = xMap.get(node);
	                int x2 = xMap.get(node.right);
	                int y = yMap.get(node);
	                setChar(x2, y, '|');
	                String value = node.toString();
	                for (int i = x1 + value.length(); i < x2; i++)
	                    setChar(i, y, '-');
	            }
	        }
	    }
	
	    //设置指定位置的字符
	    protected void setChar(int x, int y, char halfChar) {
	        char fullChar = toFullWidth(halfChar);
	        charForm.get(y).set(x, fullChar);
	    }
	
	    //半角字符转全角字符
	    private char toFullWidth(char halfChar) {
	        if (halfChar == 32)
	            return (char) 12288;
	        if (halfChar < 127)
	            return (char) (halfChar + 65248);
	        return halfChar;
	    }
	}


	@SuppressWarnings("all")
	public class Demo {
	
	    public static void main(String[] args) {
	        {
	            //验证左单旋
	            System.out.println();
	            AVLTree bst = new AVLTree();
	            bst.Insert(50);
	            bst.Insert(30);
	            bst.Insert(80);
	            bst.Insert(70);
	            bst.Insert(90);
	            bst.Insert(95);
	            AVLPrinter printer = new AVLPrinter();
	            printer.setRootNode(bst.root);
	            printer.Print();
	            System.out.println();
	        }
	        {
	            //验证右单旋
	            System.out.println();
	            AVLTree bst = new AVLTree();
	            bst.Insert(50);
	            bst.Insert(30);
	            bst.Insert(80);
	            bst.Insert(20);
	            bst.Insert(40);
	            bst.Insert(15);
	            AVLPrinter printer = new AVLPrinter();
	            printer.setRootNode(bst.root);
	            printer.Print();
	            System.out.println();
	        }
	        {
	            //验证右单旋
	            System.out.println();
	            AVLTree bst = new AVLTree();
	            bst.Insert(50);
	            bst.Insert(30);
	            bst.Insert(80);
	            bst.Insert(20);
	            bst.Insert(40);
	            bst.Insert(45);
	            AVLPrinter printer = new AVLPrinter();
	            printer.setRootNode(bst.root);
	            printer.Print();
	            System.out.println();
	        }
	        {
	            //验证右单旋
	            System.out.println();
	            AVLTree bst = new AVLTree();
	            bst.Insert(50);
	            bst.Insert(30);
	            bst.Insert(80);
	            bst.Insert(70);
	            bst.Insert(90);
	            bst.Insert(60);
	            AVLPrinter printer = new AVLPrinter();
	            printer.setRootNode(bst.root);
	            printer.Print();
	            System.out.println();
	        }
	    }
	}

在这里插入图片描述
可以看出,最终的树结构和平衡因子,和我们上面图示的情况一模一样,完全正确

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值