学习数据结构笔记(12) --- [平衡二叉搜索树(AVLTREE)]
Posted 小智RE0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习数据结构笔记(12) --- [平衡二叉搜索树(AVLTREE)]相关的知识,希望对你有一定的参考价值。
B站学习传送门–>尚硅谷Java数据结构与java算法(Java数据结构与算法)
案例情景引入
比如给出一组数字1,2,3,4,5,6
,要把它排成二叉搜索树;那是不是就得变成下面这样的树;
仔细看的话,比较像是单向链表;增删比较快,但是查询时比较慢;由于树在查询时还要检索,效率甚至会更慢;
平衡二叉搜索树(Self-balancing binary search tree);左右两棵子树的高度之差不会超过1;
平衡二叉树的常用实现有AVL树,红黑树,伸展树.
AVL树是最先发明的自平衡二叉搜索树(平衡二叉查找树)。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树;
前提是这棵树是一棵二叉搜索树
做几个案例图看看;
左旋转图解
例如,有个数列
4,3,6,5,7,8
;
形成二分搜索树如下所示,但是不符合平衡树规则
那么就需要进行左旋转
首先将当前树的根结点,复制一份,创建一个新节点;4
然后,将
当前节点 4
的左子节点
作为新节点 4 的左子节点
;
当前节点4
的右子节点6
的左子节点 5
作为新节点 4 的右子节点
然后再让
当前节点4
的右子节点6
替换当前结点4
让
当前结点6
的右子节点6
的右子节点7
作为当前节点6
的右子节点
让
新节点4
作为当前结点6的左子节点
;
平衡二叉搜索树构建完成
通过代码实现
注意,这左旋转时,仅考虑到右子树高于左子树的情况;后面会考虑右旋转与双旋转;
这里节点底层的话还是要用之前上一节的二分搜索树作为基础;
—>学习数据结构[12]之学习二分搜索树的实现
在内部类TreeNode
下定义计算树的高度,以及左子树高度,右子树高度的方法
/**
* 计算左子树的高度;
* @return 左子树的高度
*/
public int getLeftHeight()
if (leftNode == null)
return 0;
else
return leftNode.getHeight();
/**
* 计算右子树的高度
* @return 右子树的高度
*/
public int getRightHeight()
if (rightNode == null)
return 0;
else
return rightNode.getHeight();
/**
* 计算当前结点的高度,(结点内底层实现)
* @return 以当前结点出发,树的高度
*/
public int getHeight()
//注意最终还要加上当前这个结点的层数 1;
return Math.max( leftNode != null ? leftNode.getHeight() : 0,
rightNode != null ? rightNode.getHeight() : 0) + 1;
未定义使用左旋转方法之前;测试当前构造的二叉搜索树
public class AVLTreeTest
public static void main(String[] args)
//测试;
int[] array = 4,3,6,5,7,8;
AVLTree avlTree = new AVLTree();
//将数组中的数据添加到平衡二叉树中;
for (int j : array)
avlTree.addNode(new TreeNode(j));
//中序遍历这棵树;
System.out.println("中序遍历该二叉搜索树--");
avlTree.infixList();
System.out.println("------------------------");
System.out.println("未进行左旋转之前--");
System.out.println("树的高度"+avlTree.getRoot().getHeight());
System.out.println("当前树的左子树高度"+avlTree.getRoot().getLeftHeight());
System.out.println("当前树的右子树高度"+avlTree.getRoot().getRightHeight());
测试结果;
中序遍历该二叉搜索树--
TreeNodeval=3
TreeNodeval=4
TreeNodeval=5
TreeNodeval=6
TreeNodeval=7
TreeNodeval=8
------------------------
未进行左旋转之前--
树的高度4
当前树的左子树高度1
当前树的右子树高度3
那么定义左旋转方法之后;
同样,在内部类TreeNode
下定义左旋转节点方法,就按照图解分析的思路进行逻辑处理;
/**
* 左旋转二叉搜索树;
*/
public void RotateLeft()
//1.首先创建新的节点;值为当前节点
TreeNode newNode = new TreeNode(val);
//2.然后会把当前节点的左子节点设置为新节点的左子节点;
newNode.leftNode = this.leftNode;
//3.当前节点的右子节点的左子节点设置为新节点的右子节点;
newNode.rightNode = this.rightNode.leftNode;
//4.用当前节点的右子节点值替换覆盖当前节点;
this.val = this.rightNode.val;
//5,注意当前节点的值此时已经变了;这里让当前节点的右节点的右节点;设置为当前节点的右节点;
this.rightNode = this.rightNode.rightNode;
//6.将新的节点挂接到当前节点的左子节点;
this.leftNode = newNode;
在添加节点的方法底层进行调用
/**
* 添加节点的底层方法;
* @param node 要添加的节点;
*/
public void addNode(TreeNode node)
//先判断当前的节点是否为空;
if (node == null) return;
if (node.val <= this.val)
//考虑挂到左子节点;若左节点已经有了,则向左继续递归;
if (this.leftNode == null)
this.leftNode = node;
else
this.leftNode.addNode(node);
else
//右子节点;若有节点已经存在,则向右继续递归;
if (this.rightNode == null)
this.rightNode = node;
else
this.rightNode.addNode(node);
//这里先考虑右子树高于左子树时, 进行左旋转的处理;
if(getRightHeight() - getLeftHeight() >1)
RotateLeft();
注意这里并不是最终的处理,仅考虑右子树高于左子树的情况;
同样进行测试使用;
当前树的左右高度之差符合平衡二叉树的规则了
中序遍历该二叉搜索树--
TreeNodeval=3
TreeNodeval=4
TreeNodeval=5
TreeNodeval=6
TreeNodeval=7
TreeNodeval=8
------------------------
左旋转处理之后--
树的高度3
当前树的左子树高度2
当前树的右子树高度2
右旋转图解
首先,就用一组数字10,8,12,7,9,6
形成的二分搜索树作为案例;
这时想让他变成平衡的二分搜索树,就得改变一下情况;
首先,按照
当前的根结点10
复制创建一个新的节点10
然后将
当前节点10
的右子节点12
挂接到新节点10的右子节点
然后,将
当前节点10
的左子节点8
的右子节点9
设置为新节点10的左子节点
用
当前节点10
的左子节点8的值
替换当前的节点的值
然后,将
当前节点8
的左子节点8
的左子节点7
设置为当前节点8的左子节点
然后,将
新节点10
设置为当前节点8
的右子节点
右旋转的具体实现
没有进行处理右旋转之前;
大概结构即这样
在内部类TreeNode
中添加右旋转的方法,就按照图解的思路进行逻辑处理;
/**
* 右旋转二叉搜索树
*/
public void RotateRight()
//1.首先创建新的节点,值为当前的节点;
TreeNode newNode = new TreeNode(val);
//2.将当前节点的右子节点作为新节点的右子节点;
newNode.rightNode = this.rightNode;
//3.将当前节点的左子节点的右子节点 就作为新节点的左子节点;
newNode.leftNode = this.leftNode.rightNode;
//4.让当前节点的左子节点的值 替换 当前节点的值;
this.val = this.leftNode.val;
//5.将当前的节点的左子节点的左子节点接到当前节点的左子节点位置;
this.leftNode = this.leftNode.leftNode;
//6.将新节点的设置为当前节点的右子节点;
this.rightNode = newNode;
在添加方法中加入右旋转方法的调用
/**
* 添加节点的底层方法;
* @param node 要添加的节点;
*/
public void addNode(TreeNode node)
//先判断当前的节点是否为空;
if (node == null) return;
if (node.val <= this.val)
//考虑挂到左子节点;若左节点已经有了,则向左继续递归;
if (this.leftNode == null)
this.leftNode = node;
else
this.leftNode.addNode(node);
else
//右子节点;若有节点已经存在,则向右继续递归;
if (this.rightNode == null)
this.rightNode = node;
else
this.rightNode.addNode(node);
//这里在添加时考虑右子树高于左子树时, 进行左旋转的处理;
if(getRightHeight() - getLeftHeight() > 1)
RotateLeft();
//添加节点时考虑左子树高于右子树时,进行右旋转的处理;
if(getLeftHeight() - getRightHeight() > 1)
RotateRight();
再进行测试处理;
注意这里的考虑也仅是左树高于右树的特殊情况;进行处理;
双旋转处理
比如下面这种树结构的话;
经过右旋转的处理;这个还是不符合平衡二叉树的规则
它实际上是进行了右旋转,变成了这样;
那么,这里就得考虑双旋转;字面意思,根据情况左旋转和右旋转结合起来;
分析一下思路;
首先看这棵树,当前根节点的左子节点的右子树的高于左子树,那么就对当前节点的左节点7 进行左旋转
;之后挂接到根节点10下;
然后注意到此时的结构,当前根节点的左子节点的左子树高于右子树,那么对当前节点进行右旋转即可;
双旋转具体实现
这里就得考虑四种情况;
在符合左旋转时(当前节点的右子树高于左子树);
- (1)
当前节点的右子节点的左子树高于右子树
;那么就得先对当前节点的右子节点进行右旋转
;然后在对当前节点进行左旋转
; - (2)若
当前节点的右子节点的右子树高于左子树
;直接对当前节点进行左旋转
即可;
在符合右旋转时(当前节点的左子树高于右子树);
- (3)若
当前节点的左子节点的右子树高于左子树
;先对当前节点的左子节点进行左旋转
;然后在对当前节点进行右旋转
; - (4)若
当前节点的左子节点的左子树高于右子树
,直接对当前节点进行右旋转
即可;
这次的话,在内部类TreeNode
的底层添加方法处进行修改即可;
/**
* 添加节点的底层方法;
* @param node 要添加的节点;
*/
public void addNode(TreeNode node)
//先判断当前的节点是否为空;
if (node == null) return;
if (node.val <= this.val)
//考虑挂到左子节点;若左节点已经有了,则向左继续递归;
if (this.leftNode == null)
this.leftNode = node;
else
this.leftNode.addNode(node);
else
//右子节点;若有节点已经存在,则向右继续递归;
if (this.rightNode == null)
this.rightNode = node;
else
this.rightNode.addNode(node);
//添加节点时考虑右子树高于左子树, 进行左旋转的处理;
if(getRightHeight() - getLeftHeight() > 1)
//若当前节点的右子节点的左子树高于右子树; 先对当前节点的右子节点进行右旋转;
if(this.rightNode!=null && this.rightNode.getLeftHeight() > this.rightNode.getRightHeight())
this.rightNode.RotateRight();
//在对当前节点进行左旋转;
this.RotateLeft();
else
//否则直接进行左旋转;
RotateLeft();
return;
//添加节点时考虑左子树高于右子树时,进行右旋转的处理;
if(getLeftHeight() - getRightHeight() > 1)
//若当前节点的左子节点的右子树高于左子树;先对当前节点的左子节点进行左旋转;
if(this.leftNode!=null && this.leftNode.getRightHeight() > this.leftNode.getLeftHeight())
this.leftNode.RotateLeft();
//然后在对当前节点进行右旋转;
this.RotateRight();
else
RotateRight();
测试;和分析的一致;
平衡二叉搜索树代码总结
最终的总结;
/**
* @author by CSDN@小智RE0
* @date 2021-11-20 11:22
* 平衡二叉树
*/
//平衡二叉树;
class AVLTree
//根节点;
private TreeNode root;
//获取当前根结点;
public TreeNode getRoot()
return root;
/**
* 在二分搜索树中添加新节点;
* @param node 需要添加的新节点;
*/
public void addNode(TreeNode node)
//若根结点为空,则让新添加的第一个节点暂时作为根结点;
if (root == null) root = node;
else
root.addNode(node);
/**
* 中序遍历该二分搜索树;
*/
public void infixList()
if (root == null)
throw new RuntimeException("the tree is empty");
root.infixList();
/**
* 根据给定的值找到要删除的节点;(在二分树下定义)
*
* @param val 给定的数值
* @return 查找到要删除的节点;
*/
public TreeNode getTreeNode(int val)
if (root == null)
return null;
else
return root.getTreeNode(val);
/**
* 需要找到被删除节点的父亲节点;(在二分树下定义)
*
* @param val 要被删除的节点;
* @return 要被删除节点的父亲节点;
*/
public TreeNode getParentByDelete(int val)
if (root == null)
return null;
else
return root.getParentByDelete(val);
/**
* 删除结点;
*
* @param val 指定删除结点的值;
*/
public void deleteNode(int val)
if (root == null) return;
//1.先找要被删除的节点;
TreeNode wantDeleteNode = getTreeNode(val);
if (wantDeleteNode == null)
//未找到节点;
throw new RuntimeException("The node to be deleted was not found");
if ((root.leftNode == null && root.rightNode == null))
//若只有根节点,删除即可
root = null;
return;
//2.找删除结点的父亲节点;
TreeNode parentNode = getParentByDelete(val);
//一: 被删除的节点是叶子节点;
if (wantDeleteNode.leftNode == null && wantDeleteNode.rightNode == null)
//然后确认这个删除结点是它父节点的左节点还是右结点;
if (parentNode.leftNode != null && parentNode数据结构之二叉搜索树和二叉平衡树学习笔记