平衡二叉树(AVL树)
创始人
2024-01-25 06:09:32

1.简介

1.二叉排序树的问题:

如果原始是数据是排好序的(如1,2,3,4,5,6),那么最终创建的二叉排序树的结构就会变成一条斜线,类似于一条单链表,此时如果需要查找/插入某个元素就要一个一个元素的比较,这样就没有优势了.由于每次都要比较左子树,其查询速度甚至比单链表还慢;

在这里插入图片描述

2.对于任意二叉排序树中任意一个非叶子节点,左右两棵子树的高度差的绝对值不超过1,这样的二叉排序树就是平衡二叉树或者平衡二叉搜索树(Self-balancing binary search tree),平衡二叉树又被称为AVL树;

如图:
在这里插入图片描述

3.平衡二叉树中可以保证查询效率较高;

4.平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等;

2.案例-(左/右)单旋转

1.要求:

给定数列 {4,3,6,5,7,8},创建出对应的平衡二叉树;

给定数列 {10,12,8,9,7,6},创建出对应的平衡二叉树;

2.左旋转思路分析:
在这里插入图片描述

3.右旋转思路分析:
在这里插入图片描述

3.代码实现

//节点对象
class Node {//节点的属性int value;Node left;Node right;public Node(int value) {this.value = value;}@Overridepublic String toString() {return "Node[" + "value=" + value + "]";}/*** 右旋转*/public void rightRotate() {//创建一个新的节点,值为当前节点(根节点)的值Node newNode = new Node(this.value);//把新节点的右子树设置为当前节点右子树newNode.right = this.right;//把新节点的左子树设置为当前节点的左子树的右子树newNode.left = this.left.right;//把当前节点的值设置为左子节点的值//左子节点上移变成根节点this.value = this.left.value;//此时当前节点(根节点)就是之前(根节点)的左子节点,然后将当前节点的左子节点设置为之前左子节点的左子节点//即跳过了之前(根节点)的左子节点,将之前(根节点)的左子节点舍弃了this.left = this.left.left;//将当前节点的右子节点设置为新节点this.right = newNode;}/*** 左旋转*/public void leftRotate() {//创建一个新的节点,值为当前节点(根节点)的值Node newNode = new Node(this.value);//把新节点的左子树设置为当前节点的左子树newNode.left = this.left;//把新节点的右子树设置为当前节点的右子树的左子树newNode.right = this.right.left;//把当前节点的值替换为右子节点的值//左子节点上移变成根节点this.value = this.right.value;//此时当前节点(根节点)就是之前(根节点)的右子节点,然后将当前节点的右子节点设置为之前右子节点的右子节点//即跳过了之前(根节点)的右子节点,将之前(根节点)的右子节点舍弃了this.right = this.right.right;//将当前节点的左子节点设置为新节点this.left = newNode;}/*** 返回右子树的高度** @return*/public int rightHeight() {if (this.right == null) {return 0;}return this.right.height();}/*** 返回左子树的高度** @return*/public int leftHeight() {if (this.left == null) {return 0;}return this.left.height();}/*** 返回该节点为根节点的树的高度** @return*/public int height() {return Math.max(this.left == null ? 0 : this.left.height(), this.right == null ? 0 : this.right.height()) + 1;}/*** 查找要删除的节点的父节点** @param value 要删除的节点的值* @return*/public Node searchParent(int value) {//当前节点就是要删除节点的父节点if ((this.left != null && this.left.value == value) ||(this.right != null && this.right.value == value)) {return this;} else {if (value < this.value && this.left != null) {return this.left.searchParent(value);} else if (value >= this.value && this.right != null) {//注意: 判断大于的时候有个等号,因为在添加节点时遇到相同值的节点是放在右子树中!!!return this.right.searchParent(value);} else {//找不到父节点return null;}}}/*** 查找要删除的节点** @param value 要删除的节点的值* @return*/public Node search(int value) {if (this.value == value) {return this;} else if (this.value > value) {//如果要查找的节点小于当前节点,递归左子树if (this.left == null) {return null;}return this.left.search(value);} else {if (this.right == null) {return null;}return this.right.search(value);}}/*** 节点中序遍历: 左->中->右*/public void infixOrder() {if (this.left != null) {this.left.infixOrder();}System.out.println(this);if (this.right != null) {this.right.infixOrder();}}/*** 添加节点(递归)** @param node 传入的节点*/public void add(Node node) {if (null == node) {return;}//判断传入的节点的值与当前子树的根节点的值的关系if (node.value < this.value) {if (this.left == null) {//如果当前节点的左子节点为null,直接将传入节点放到左子节点位置this.left = node;} else {//如果左子节点不为空,那么基于左子节点进行递归,找到合适的位置存放传入的节点this.left.add(node);}} else {if (this.right == null) {//如果当前节点的右子节点为null,直接将传入节点放到右子节点位置this.right = node;} else {//如果右子节点不为空,那么基于右子节点进行递归,找到合适的位置存放传入的节点this.right.add(node);}}//当添加一个节点之后,判断左右两棵子树的高度差是否大于1,如果大于,则需要进行对应的处理if (this.rightHeight() - this.leftHeight() > 1) {this.leftRotate();}else if (this.leftHeight() - this.rightHeight() > 1){this.rightRotate();}}
}

4.案例-双旋转

问题:

前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换.比如:

int[] arr = {10, 11, 7, 6, 8, 9}; //运行原来的代码可以看到,并没有转成AVL树;

int[] arr = {2,1,6,5,7,3}; //运行原来的代码可以看到并没有转成树;

在这里插入图片描述

双旋转思路分析:

①.当符合右旋转的条件时(当前节点的左子树的右子树的高度大于当前节点的左子树的左子树的高度):

A.先要对当前节点的左子树进行左旋转;

B.再对当前节点进行右旋转操作即可;


②.当符合左旋转的条件时(当前节点的右子树的左子树的高度大于当前节点的右子树的右子树的高度):

A.先要对当前节点的右子树进行右旋转;

B.在对当前节点进行左旋转操作即可;

代码实现:

//节点对象
class Node {//节点的属性int value;Node left;Node right;public Node(int value) {this.value = value;}@Overridepublic String toString() {return "Node[" + "value=" + value + "]";}/*** 右旋转*/public void rightRotate() {//创建一个新的节点,值为当前节点(根节点)的值Node newNode = new Node(this.value);//把新节点的右子树设置为当前节点右子树newNode.right = this.right;//把新节点的左子树设置为当前节点的左子树的右子树newNode.left = this.left.right;//把当前节点的值设置为左子节点的值//左子节点上移变成根节点this.value = this.left.value;//此时当前节点(根节点)就是之前(根节点)的左子节点,然后将当前节点的左子节点设置为之前左子节点的左子节点//即跳过了之前(根节点)的左子节点,将之前(根节点)的左子节点舍弃了this.left = this.left.left;//将当前节点的右子节点设置为新节点this.right = newNode;}/*** 左旋转*/public void leftRotate() {//创建一个新的节点,值为当前节点(根节点)的值Node newNode = new Node(this.value);//把新节点的左子树设置为当前节点的左子树newNode.left = this.left;//把新节点的右子树设置为当前节点的右子树的左子树newNode.right = this.right.left;//把当前节点的值替换为右子节点的值//左子节点上移变成根节点this.value = this.right.value;//此时当前节点(根节点)就是之前(根节点)的右子节点,然后将当前节点的右子节点设置为之前右子节点的右子节点//即跳过了之前(根节点)的右子节点,将之前(根节点)的右子节点舍弃了this.right = this.right.right;//将当前节点的左子节点设置为新节点this.left = newNode;}/*** 返回右子树的高度** @return*/public int rightHeight() {if (this.right == null) {return 0;}return this.right.height();}/*** 返回左子树的高度** @return*/public int leftHeight() {if (this.left == null) {return 0;}return this.left.height();}/*** 返回该节点为根节点的树的高度** @return*/public int height() {return Math.max(this.left == null ? 0 : this.left.height(), this.right == null ? 0 : this.right.height()) + 1;}/*** 查找要删除的节点的父节点** @param value 要删除的节点的值* @return*/public Node searchParent(int value) {//当前节点就是要删除节点的父节点if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) {return this;} else {if (value < this.value && this.left != null) {return this.left.searchParent(value);} else if (value >= this.value && this.right != null) {//注意: 判断大于的时候有个等号,因为在添加节点时遇到相同值的节点是放在右子树中!!!return this.right.searchParent(value);} else {//找不到父节点return null;}}}/*** 查找要删除的节点** @param value 要删除的节点的值* @return*/public Node search(int value) {if (this.value == value) {return this;} else if (this.value > value) {//如果要查找的节点小于当前节点,递归左子树if (this.left == null) {return null;}return this.left.search(value);} else {if (this.right == null) {return null;}return this.right.search(value);}}/*** 节点中序遍历: 左->中->右*/public void infixOrder() {if (this.left != null) {this.left.infixOrder();}System.out.println(this);if (this.right != null) {this.right.infixOrder();}}/*** 添加节点(递归)** @param node 传入的节点*/public void add(Node node) {if (null == node) {return;}//判断传入的节点的值与当前子树的根节点的值的关系if (node.value < this.value) {if (this.left == null) {//如果当前节点的左子节点为null,直接将传入节点放到左子节点位置this.left = node;} else {//如果左子节点不为空,那么基于左子节点进行递归,找到合适的位置存放传入的节点this.left.add(node);}} else {if (this.right == null) {//如果当前节点的右子节点为null,直接将传入节点放到右子节点位置this.right = node;} else {//如果右子节点不为空,那么基于右子节点进行递归,找到合适的位置存放传入的节点this.right.add(node);}}//当添加一个节点之后,判断左右两棵子树的高度差是否大于1,如果大于,则需要进行对应的处理if (this.rightHeight() - this.leftHeight() > 1) {//双旋转: 如果当前节点的右子树的左子树的高度大于当前节点的右子树的右子树高度if (this.right != null && this.right.leftHeight() > this.right.rightHeight()) {//先要对当前节点的右子树进行右旋转this.right.rightRotate();}//在对当前节点进行左旋转操作即可this.leftRotate();//这里的return千万不能省略!!!return;}if (this.leftHeight() - this.rightHeight() > 1) {//双旋转: 如果当前节点的左子树的右子树的高度大于他的右子树高度if (this.left != null && this.left.rightHeight() > this.left.leftHeight()) {//先要对当前节点的左子树进行左旋转this.left.leftRotate();}//在对当前节点进行右旋转this.rightRotate();//这里的return千万不能省略!!!return;}}
}

相关内容

热门资讯

什么是419 419是什么意思... 嗨,辣条陪你理智吃瓜最近刚还想着10月特殊时期可能没啥瓜了,结果一到月底就来了。孟美岐冷不丁就突然上...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
什么是419 419是什么意思... 嗨,辣条陪你理智吃瓜最近刚还想着10月特殊时期可能没啥瓜了,结果一到月底就来了。孟美岐冷不丁就突然上...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...