自平衡二叉树的难点在于左旋和右旋,详细步骤如下:
/**
* 自平衡二叉树左旋:
* 1、以当前根节点的值创建一个新的节点 new Node(this.value)
* 2、把新节点的左子树设置为当前节点的左子树 newNode.left = this.left
* 3、新节点的右子树设置为当前节点右子树的左子树 newNode.right = this.right.left
* 4、当前节点的值替换为右子节点的值 this.value = this.right.value
* 5、当前节点的右子树设置为右子树的右子树 this.right = this.right.right(本来右边很长,这样做后,就变短了)
* 6、当前节点的左子树设置为前几步新建的那个节点 this.left = newNode
*
* 做完左旋后,剩余之前根节点指向的右子树,这个树现在没人指向它,在java中会被垃圾回收。
*
* 自平衡二叉树右旋是完全类似的:
* 1、以当前根节点的值创建一个新的节点
* 2、把新节点的右子树设置为当前节点的右子树
* 3、新节点的左子树设置为当前节点左子树的右子树 n
* 4、当前节点的值替换为左子节点的值
* 5、当前节点的左子树设置为左子树的左子树
* 6、当前节点的右子树设置为前几步新建的那个节点
*
*
* 还有一种情况,可能会导致一棵树左旋后还不满足自平衡二叉树(右选类似),这时候想要做的是:双旋转
* 在符合右旋转条件时:
* 1、如果左子树的右子树高度大于它的左子树的高度
* 2、先对当前这个节点的左节点进行左旋(变成了一长条,这时候已经比较合适做右旋转)
* 3、再对当前节点进行右旋
*
*/
从节点类创建、平衡树创建到测试的带注解详细代码:
package cn.edu.uestc.datastrure.Tree;
import java.util.Map;
public class AVLTreeDemo {
public static void main(String[] args) {
int[] arr = {10,11,7,6,8,9};
AVLTree avlTree = new AVLTree();
//添加节点
for (int i = 0; i < arr.length; i++) {
avlTree.add(new Node04(arr[i]));
}
avlTree.infixOrder();
System.out.println("-----------------");
//avlTree.delNode(7);
//avlTree.infixOrder();
//由于寻找子树高度的方法只写在Node类中,没写到avl树上,所以想获取高度,就需要用具体节点去调用
//比如可以操作avlTree的root节点(下面是没加旋转代码时的结果)
System.out.println(avlTree.root.height());//3
System.out.println(avlTree.root.leftHeight());//2
System.out.println(avlTree.root.rightHeight());//2
}
}
class AVLTree{
Node04 root;
public AVLTree(Node04 root) {
this.root = root;
}
public AVLTree() {
}
/**
* 为树添加节点
*/
public void add(Node04 node){
if (root == null){
root = node;
}else {
root.add(node);
}
}
/**
* infix遍历
*/
public void infixOrder(){
if (root == null){
System.out.println("当前树为空");
}else {
root.infixOrder();
}
}
/**
* 查找目标节点
* @param value
* @return
*/
public Node04 searchTargetNode(int value){
if (this.root == null){
System.out.println("当前节点为空");
return null;
}else {
return root.searchTargetNode(value);
}
}
/**
* 查找目标节点的父节点
*/
public Node04 searchParent(int value){
if (this.root == null){
System.out.println("当前二叉树为空");
return null;
}else {
return this.root.searchParent(value);
}
}
/**
* 找到targetNode右子树最小值,值放到targetNode中,并删除这个节点
* @param node 传进来的是targetNode.left
* @return 返回targetNode右子树最小值,之后用于把这个值赋给targetNode
*/
public int delRightTreeMin(Node04 node){
if (node != null) {//其实这个可以不用判断,因为输入的时候在外层调用就判断过了
while (node.getLeft() != null) {
node = node.getLeft();
}
}
//执行到这里node03.getLeft() == null,说明以及找到右子树的最小值(向左就是小的)
int temp = node.getValue();
delNode(node.getValue());
return temp;
}
/**
* 删除节点的方法
* @param value
*/
public void delNode(int value){
if (root == null){
return;
}else {
//先获取当前节点
Node04 targetNode = root.searchTargetNode(value);
if (targetNode == null){
System.out.println("未找到对应value值的节点");
return;
}
//如果我们发现当前这颗二叉排序树只有一个结点
if (root.getRight() == null && root.getLeft() == null){
root = null;
return;
}
//去寻找当前节点的父节点
Node04 parent = this.searchParent(value);
//开始分情况讨论; 1、要删除的节点是叶子节点
if (targetNode.getLeft() == null && targetNode.getRight() == null){
System.out.println("要删除的节点是叶子节点");
if (parent.getLeft().getValue() == value){
parent.setLeft(null);
}else {
parent.setRight(null);
}
} else if (targetNode.getRight() != null && targetNode.getLeft() != null) {
//2、目标节点有两颗子树
int min = this.delRightTreeMin(targetNode.getRight());
targetNode.setValue(min);
}else {
//3、目标节点只有一颗子树
if (targetNode.getLeft() != null){ //目标节点有左节点
if (parent != null){//等于空直接挂在root上
if (parent.getLeft() != null && parent.getLeft().getValue() == value){
//目标节点是parent的左节点
//不能下面这样写!这样只是把值该了,我们应该该变指向,之后删除节点没人指定后,被垃圾回收!
//parent.getLeft().setValue(targetNode.getLeft().getValue());
parent.setLeft(targetNode.getLeft());
}
}else {
root.setLeft(targetNode.getLeft());
}
}else {//目标节点只有右节点
if (parent != null){
if (parent.getRight() != null && parent.getRight().getValue() == value){
parent.setRight(targetNode.getRight());
}
}else {
parent.setLeft(targetNode.getRight());
}
}
}
}
}
}
class Node04{
private int value;
private Node04 left;
private Node04 right;
/**
* 添加节点的方法(由于自平衡二叉树可以由二叉排序树左旋、右旋而来。所以添加、删除等方法可以先按照二叉排序树写,先构成一个二叉排序树)
* 所以只要是二叉排序树的添加删除,都要注意 左<中<右 的规则
* @param node
*/
public void add(Node04 node){
if(node == null){
System.out.println("当前节点为空,无法添加");
return;
}
if (node.value < this.value){
if (this.left == null){
this.left = node;
}else {
this.left.add(node);
}
}
if (node.value > this.value){
if (this.right == null){
this.right = node;
}else {
this.right.add(node);
}
}
//当添加完一个结点后,如果: (右子树的高度-左子树的高度) > 1 , 左旋转
if ((rightHeight() - leftHeight()) > 1){
//如果它的右子树的左子树的高度大于它的右子树的右子树的高度
if (right!=null && this.right.leftHeight() > this.right.rightHeight()){
//先对右子结点进行右旋转
this.right.rightRotate();
//然后在对当前结点进行左旋转
this.leftRotate();
}else {
//直接左旋
leftRotate();
}
return;//千万不能丢,如果满足上面条件执行完毕后就完成调整了,不能再向下调整。
//如果上面的return不写,下面应该用else if(....)
}
//当添加完一个结点后,如果 (左子树的高度 - 右子树的高度) > 1, 右旋转
if(leftHeight() - rightHeight() > 1) {
//如果它的左子树的右子树高度大于它的左子树的高度
if(left != null && left.rightHeight() > left.leftHeight()) {
//先对当前结点的左结点(左子树)->左旋转
left.leftRotate();
//再对当前结点进行右旋转
rightRotate();
} else {
//直接进行右旋转即可
rightRotate();
}
}
}
/**
* 搜索当前节点(还没测试)
* @param value
* @return
*/
public Node04 searchTargetNode(int value){
if (this.value == value){
return this;
}else {
if (this.left != null && value < this.value){
return this.left.searchTargetNode(value);
}else if (this.right != null && value > this.value){
return this.right.searchTargetNode(value);
}else {
System.out.println("未找到目标节点");
return null;
}
}
}
/**
* 搜索当前节点的父节点
* @return
*/
public Node04 searchParent(int value){
if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)){
return this;
}else {
if (this.left != null && value < this.value){
return this.left.searchParent(value);
}else if (this.right != null && value < this.value){
return this.right.searchParent(value);
}else {
return null;
}
}
}
/**
* 中序遍历
*/
public void infixOrder(){
if (this.left != null){
this.left.infixOrder();
}
System.out.println(this);
if (this.right != null){
this.right.infixOrder();
}
}
//avl自平衡二叉树中,左右子树的高度是很重要的参数
/**
* 获取子树高度
* @return
*/
public int height(){
//很巧妙的代码,递归到最后了,第一次+1,之后栈结构退出来,每一轮都+1,知道第一次调用,正好计算好了树的高度
return Math.max(left == null ? 0 : left.height(),right == null ? 0 : right.height()) +1;
}
/**
* 返回左子树的高度
* @return
*/
public int leftHeight(){
if (this.left == null){
return 0;
}else {
return left.height();
}
}
/**
* 返回右子树的高度
* @param
*/
public int rightHeight(){
if (this.right == null){
return 0;
}else {
return right.height();
}
}
/**
* 左旋
*/
private void leftRotate(){
// 1、以当前根节点的值创建一个新的节点 new Node(this.value)
Node04 newNode = new Node04(this.value);
// 2、把新节点的左子树设置为当前节点的左子树 newNode.left = this.left
newNode.left = this.left;
// 3、新节点的右子树设置为当前节点右子树的左子树 newNode.right = this.right.left
newNode.right = this.right.left;
// 4、当前节点的值替换为右子节点的值 this.value = this.right.value
this.value = this.right.value;
// 5、当前节点的右子树设置为右子树的右子树 this.right = this.right.right(本来右边很长,这样做后,就变短了)
this.right = right.right;
//6、当前节点的左子树设置为前几步新建的那个节点 this.left = newNode
this.left = newNode;
}
/**
* 右旋
*/
private void rightRotate(){
// 1、以当前根节点的值创建一个新的节点
Node04 newNode = new Node04(this.value);
// 2、把新节点的右子树设置为当前节点的右子树
newNode.right = this.right;
// 3、新节点的左子树设置为当前节点左子树的右子树
newNode.left = this.left.right;
// 4、当前节点的值替换为左子节点的值
this.value = this.left.value;
// 5、当前节点的左子树设置为左子树的左子树
this.left = left.left;
//6、当前节点的右子树设置为前几步新建的那个节点
this.right = newNode;
}
public Node04(int value) {
this.value = value;
}///构造方法用于new对象,只要传入值就好,每个节点的左右关系在add()添加节点的方法里处理好了
@Override
public String toString() {
return "Node04{" + "value=" + value + "}";
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public Node04 getLeft() {
return left;
}
public void setLeft(Node04 left) {
this.left = left;
}
public Node04 getRight() {
return right;
}
public void setRight(Node04 right) {
this.right = right;
}
}