数据结构 ---[实现平衡树(AVL Tree)]
Posted 小智RE0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构 ---[实现平衡树(AVL Tree)]相关的知识,希望对你有一定的参考价值。
写在前面
实际上,之前已经更新过一篇关于平衡树的笔记了;—>笔记链接;
学习数据结构笔记(12) — [平衡二叉搜索树(AVLTREE)]之前的实现方式需要计算出当前节点的父节点的索引;…左旋右旋实现方式步骤比较明确;
不足之处是仅实现了添加节点时维持二分树的平衡;
本次的平衡树具体操作方式和之前的那种略有不同;所以记录一下吧…
这里的基础会以之前的二分搜索树实现作为基础,在基础上进行改动;笔记链接—>
数据结构 (五) — [二分搜索树 (Java)]
这里首先会在节点内定义属性height
表示高度;
然后在树中定义基础方法来获取树的高度;
基础方法获取树的平衡因子
:—>左右子树的高度差;
定义了方法 判断当前树是否为二分搜索树
(判断中序遍历的结果是否为由小到大排序);
当前树是否为平衡树
(平衡因子是否超过1);
以及添加/删除节点时左旋右旋操作维持二分树的平衡;
如图,这不是平衡树
右旋转
这棵树左子树偏高;要进行右旋转
那么,仅需两步;
首先将取出节点8的右子节点9;将节点10
作为节点8
的右子节点
然后让
节点9
作为节点10
的左子节点
左旋转
这棵树右子树偏高,需要左旋转
只需两步;
首先将节点6
的左子节点5取出,记录;
将节点4作为节点6的左子节点
然后将节点5作为节点4的右子节点
双旋转
有特殊情况的,就得出动双旋转了;
像下面这种就是需要先进行左旋转处理;然后进行右旋转处理;
先对节点7进行左旋转处理
然后对节点10进行右旋转处理
具体代码
package com.company.e_avltree;
import java.util.*;
/**
* @author 小智RE0
* @Date: 2021/12/10/
*/
//由于二分搜索树,需要对元素的大小进行比较;
public class AVLStartTree<T extends Comparable<T>>
//定义内部类结点;
class Node
T val;
//左子树,右子树;
Node leftNode;
Node rightNode;
//树的高度;
int height;
public Node(T val)
//初始化时,树的高度为1;
this.height = 1;
this.val = val;
this.leftNode = null;
this.rightNode = null;
//定义树的根结点;
private Node root;
//定义树结点的个数;
private int size;
public AVLStartTree()
this.root = null;
this.size = 0;
//判空;
public boolean isEmpty()
return this.root == null;
/**
* 获取以指定结点为树的高度
*
* @param node 节点
*/
public int getHeight(Node node)
if (node == null)
return 0;
else
return node.height;
/**
* 获取到以指定结点出发,树的高度因子;左右子树的高度差;
*/
public int getHeightFactor(Node node)
if (node == null)
return 0;
//这里没有取绝对值,可能会出现高度因子为负数的情况
return getHeight(node.leftNode) - getHeight(node.rightNode);
/**
* 判断是否为二分搜索树;
*/
public boolean isBSTTree()
return isBSTTree(root);
/**
* 判断是否为二分搜索树
*/
private boolean isBSTTree(Node node)
List<T> values = new ArrayList<>();
//调用递归方法;将值存到values里面;
inordertraversal(node, values);
//对遍历的值进行遍历,若为二分树,实际上会由小到大排序,若不符合,直接结束;
//注意;由于是让当前节点和后一个结点进行比较,所以循环的次数实际上会少一次;
for (int i = 0; i < values.size() - 1; i++)
//若出现前面的比后面大,直接返回;
if (values.get(i).compareTo(values.get(i + 1)) > 0)
return false;
return true;
/**
* 中序遍历,将遍历值存入集合
*
* @param node 节点
* @param values 遍历的值
*/
private void inordertraversal(Node node, List<T> values)
//递归终止条件;
if (node == null)
return;
//左中右;
inordertraversal(node.leftNode, values);
values.add(node.val);
inordertraversal(node.rightNode, values);
/**
* 判断是否为平衡树;
*/
public boolean isAVLTree()
return isAVLTree(root);
/**
* 判断是否为平衡树
*
* @param node 节点
*/
private boolean isAVLTree(Node node)
//递归终止的条件;
if (node == null)
return true;
//判断当前节点的平衡因子;
if (Math.abs(getHeightFactor(node)) > 1)
return false;
//递归计算左右子树的高度因子;
return isAVLTree(node.leftNode) && isAVLTree(node.rightNode);
/**
* 左旋转处理
*
* @param curNode 当前节点
*/
public Node RotateLeft(Node curNode)
//取得当前节点的右子节点;
Node rNode = curNode.rightNode;
//将右子节点的左子节点取出,标记;
Node rLNode = rNode.leftNode;
//将当前节点作为它右子节点的左子节点;
rNode.leftNode = curNode;
//将当前节点的右子节点的 左子节点 挂接到当前节点的右子节点下;
curNode.rightNode = rLNode;
//更改树的高度;
curNode.height = Math.max(getHeight(curNode.leftNode), getHeight(curNode.rightNode)) + 1;
rNode.height = Math.max(getHeight(rNode.leftNode), getHeight(rNode.rightNode)) + 1;
return rNode;
/***
* 右旋转处理
* @param curNode 当前节点
*/
public Node RotateRight(Node curNode)
//取得当前节点的左子节点;
Node lNode = curNode.leftNode;
//将左子节点的右子节点取出,标记;
Node lRNode = lNode.rightNode;
//将当前节点作为它左子节点的右子节点;
lNode.rightNode = curNode;
//将当前节点的左子节点 的 右子节点挂接到到当前节点的左子节点下;
curNode.leftNode = lRNode;
//更改树的高度;
curNode.height = Math.max(getHeight(curNode.leftNode), getHeight(curNode.rightNode)) + 1;
lNode.height = Math.max(getHeight(lNode.leftNode), getHeight(lNode.rightNode)) + 1;
return lNode;
/**
* 添加元素
*
* @param ele 指定的元素
*/
public void add(T ele)
//判断节点是否存在;
if (contains(ele) != null)
return;
//每次将创建的根节点返回;
root = add(root, ele);
this.size += 1;
//递归添加元素的底层;
private Node add(Node node, T ele)
//不存在就创建;
if (node == null)
return new Node(ele);
//添加的元素和之前的根结点进行比较;
if (ele.compareTo(node.val) > 0)
node.rightNode = add(node.rightNode, ele);
else
node.leftNode = add(node.leftNode, ele);
//添加之后;需要更新当前树的高度,----->计算左右子树的高度+1 作为当前树的高度;
node.height = Math.max(getHeight(node.leftNode), getHeight(node.rightNode)) + 1;
//左旋转,右旋转,以及左右旋转情况判断;
Node resNode = null;
//维持平衡;
//极度偏左;
if (getHeightFactor(node) > 1 && getHeightFactor(node.leftNode) >= 0)
//直接右旋;
resNode = RotateRight(node);
//极度偏右;
else if (getHeightFactor(node) < -1 && getHeightFactor(node.rightNode) <= 0)
//直接左旋;
resNode = RotateLeft(node);
//左树先偏高,后面右子树偏高;
else if (getHeightFactor(node) > 1 && getHeightFactor(node.leftNode) < 0)
//先左旋后右旋;
node.leftNode = RotateLeft(node.leftNode);
resNode = RotateRight(node);
//右树先偏高,后面左子树偏高;
else if (getHeightFactor(node) < -1 && getHeightFactor(node.rightNode) > 0)
//先右旋再左旋;
node.rightNode = RotateRight(node.rightNode);
resNode = RotateLeft(node);
else
//若平衡,就直接用node节点的值;
resNode = node;
return resNode;
/**
* 删除指定数值;
* @param val 指定数值;
*/
public T removeAssignVal(T val)
if (root == null)
System.out.println("空树不用删除");
return null;
//先去找是否存在; 要去新建方法;
Node node = findAssignNode(root, val);
//是否找到;
if (node != null)
//删除后的剩余结点挂到根结点之后;
root = removeAssignVal(root, val);
//元素个数减少;
this.size -= 1;
return node.val;
return null;
/**
* 删除指定结点的底层实现;在根结点为node的二分树中删除指定结点;
*
* @param node 指定根结点出发
* @param val 指定值
*/
private Node removeAssignVal(Node node, T val)
//找到结点时;
if (node.val.compareTo(val) == 0)
//第一种情况==> 该删除结点的左树为空;
if (node.leftNode == null)
//删除当前的结点后;把原来后面的右子树挂到删了结点的位置;
Node RNode = node.rightNode;
node.rightNode = null;
return RNode;
//第二种情况,右树为空;
else if (node.rightNode == null)
//删除当前的结点后;把原来后面的左子树挂到删了结点的位置;
Node LNode = node.leftNode;
node.leftNode = null;
return LNode;
else
//情况三; 左树 右树 都不为空时,可以选择找前驱结点(左子树那块区域的最大值) 或 后继结点(右子树那块区域的最小值)代替删除结点;
//这里选择找后继节点;
Node minR = findMinNodeRecurve(node.rightNode);
Node minRNode = removeMinNode(node.rightNode);
//后继节点放到删除的结点位置;
minR.leftNode = node.leftNode;
minR.rightNode = minRNode;
node.leftNode = null;
node.rightNode = null;
return minR;
//递归;
//找结点时,若当前的根结点比指定的值还大,就去左边找; 否则去右边找;
if (node.val.compareTo(val) > 0)
node.leftNode = removeAssignVal(node.leftNode, val);
else
node.rightNode = removeAssignVal(node.rightNode, val);
Node resNode = node;
//更新高度;
resNode.height = Math.max(getHeight(resNode.leftNode),getHeight(resNode.rightNode))+1;
//维持平衡操作;
//极度偏左;
if (getHeightFactor(resNode) > 1 && getHeightFactor(resNode.leftNode) >= 0)
//直接右旋;
resNode = RotateRight(resNode);
//极度偏右;
else if (getHeightFactor(resNode) < -1 && getHeightFactor(resNode.rightNode) <= 0)
//直接左旋;
resNode = RotateLeft(resNode);
//左树先偏高,后面右子树偏高;
else if (getHeightFactor(resNode) > 1 && getHeightFactor(resNode.leftNode) < 0)
//先左旋后右旋;
resNode.leftNode = RotateLeft(resNode.leftNode);
resNode = RotateRight(resNode);
//右树先偏高,后面左子树偏高;
else if (getHeightFactor(resNode) < -1 && getHeightFactor(resNode.rightNode) > 0)
//先右旋再左旋;
resNode.rightNode = RotateRight(resNode.rightNode);
resNode = RotateLeft(resNode);
return resNode;
//中序遍历; 稍作修改,让这个遍历结果存入字符串;
public List<String> middleOrder()
//若二叉树为空,则直接返回null空值;
if (root == null)
return null;
//遍历的结果存入集合中;
List<String> list = new ArrayList<>();
middleOrder(root, list);
return list;
//中序遍历底层;
private void middleOrder(Node node, List<String> list)
//递归结束点,若到达最后一层的叶子节点就停止;
if (node == null)
return;
//中序遍历= 先遍历左子树 ==> 获取中间结点 ==> 遍历右子树
middleOrder(node.leftNode, list);
list.add(node.val + "<---高度因子--->" + getHeightFactor(node));
middleOrder(node.rightNode, list);
/**
* 输出打印树的中序遍历信息;
*/
@Override
public String toString()
List<String> list = middleOrder();
list.forEach(a -> System.out.println(a + "\\t"));
return "";
//查询元素;
public Node contains(T ele)
if (root == null)
return null;
return contains(root, ele);
//查询元素的底层;
private Node contains(Node root, T ele)
//递归结束点;
if以上是关于数据结构 ---[实现平衡树(AVL Tree)]的主要内容,如果未能解决你的问题,请参考以下文章
平衡二叉树(Balanced Binary Tree 或 Height-Balanced Tree)又称AVL树