数据结构 AVL树 java实现

avl树

AVL树本质上还是一棵二叉搜索树,它的特点是:
1.本身首先是一棵二叉搜索树。
2.带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。
也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)

问题解析:
为什么会出现avl树,二叉树中不断的新增元素,但是由于顺序问题造成最终树的形态"畸形",导致搜索的时间复杂度过高,寻求一种方法在新增元素时,通过树中的边旋转,然后得到一个较为平衡的树,即两个子树的度之差小于2
avatar

其中涉及到问题:为什么旋转,怎么旋转,有什么特征进行判断

定义节点信息如下

class Node {
    public int data;        //本节点携带数据
    public Node leftChild;  //左子树节点
    public Node rightChild; //右子树节点
    public Node parent;     //父节点
    public int depth;       //深度 该节点距离叶节点的最大距离
    public int dep_sub;     //子树深度差 右子树的depth 减 左子树的depth

}

1.为什么旋转
在树新增元素的时候会遇到左右不对称的情况

2.怎么旋转
其实我在看这个算法的时候,不晓得什么叫左旋转,什么叫右旋转,以下为我自己参照网上教程上的定义:
我总结出来就左旋转和右旋转,而一些教程中的左右旋转,右左旋转就是一些组合.
图形速记
pic
左旋转:较高节点为偏左,较高节点向下旋转
右旋转:较高节点偏右,较高节点向下旋转

而在新增节点时我们会遇到以下四类

左左情况
某个节点的左子树的左子树上新增,或新增某个子节点,导致当前的节点的右子树与左子树深度之差大于等于-2
pic1
以第一种情况为例
pic2

这个时候有的小伙伴就要问了,这个只是因为X节点中没有右子树,有右子树咋办嘞?这里如果X有右子树的话,我们就将X的右子树赋给Y的左子树,向第二个例子一样

pic3
(实际上有没有右子树是一样的,都是将X.rightChild赋值给Y.liftChild)
对于左左状态,我的记忆方法就是左左子树的新增出现问题,此时需要对子树差不符合规范的节点(dep_sub<=2)进行右旋操作

右右情况
avatar
和左左情况一样
pic
pic

左右情况
这种情况的产生是因为左子树的右子树出现了问题,我们需要先进行左旋转,后进行右旋转.从而达到平衡状态
pic
对于第一种状态
pic
对于第二种状态
pic

右左情况:
这种情况的产生是因为右子树的左子树出现了问题,我们需要先进行右旋转,后进行左旋转,从而达到平衡状态
状态示例
pic
对于第一种情况
pic
对于第二种情况
pic

那么如何进行判定这四种情况呢?
这四种情况取决于节点的左右子树深度差(dep_sub)

if (node.dep_sub > 1) {
    // 右比左长,判断右子树中那条路出问题
    if (node.rightChild.dep_sub > 0) {
        // 右右情况,进行左旋
        leftRotate(node, node.rightChild);
    } else if (node.rightChild.dep_sub < 0) {
        // 右左情况,进行右旋,再进行左旋
        rightRotate(node.rightChild.leftChild, node.rightChild);
        leftRotate(node, node.rightChild);
    } else {
        // 右中情况 左旋 在删除节点时出现
        leftRotate(node, node.rightChild);
    }

} else if (node.dep_sub < -1) {
    // 左比右长,判断左子树出现过长
    if (node.leftChild.dep_sub < 0) {
        // 左左情况,进行右旋
        rightRotate(node.leftChild, node);
    } else if (node.leftChild.dep_sub > 0) {
        // 左右情况,进行左旋,再进行右旋
        leftRotate(node.leftChild, node.leftChild.rightChild);
        rightRotate(node.leftChild, node);

    } else {
        // 左中情况 右旋 在删除节点时出现
        rightRotate(node.leftChild, node);
    }
}

这是这四种方案的不同处理方案,接下来是对于此种树的具体java代码实现

删除节点

而我们在删除时,可能遇到的情况有
以下几种情况:
1.删除节点为叶子节点
直接删除该被删除节点的父节点引用
2.删除节点有且只有一个子节点
将该子节点直接与被删除节点的父节点进行建立连接
3.删除节点有两个子节点
考虑哪边的子树较长,将较长子树向上移动,使用较长子树值最靠近删除数据的值,一般是左子树的最右子树或右子树的最左子树
顶替删除数据节点的位置例如:

pic

以下为删除的具体代码:

public boolean del(int data) {
    Node dataNode = find(data);
    if (dataNode == null) {
        // 未找到
        return false;
    }
    //
    if (dataNode.leftChild == null && dataNode.rightChild == null) {
        // 若有无子节点
        changeParentQuate(dataNode, null);
        updateDep(dataNode.parent);
        
    } else if (dataNode.leftChild != null && dataNode.rightChild == null) {
        // 有一个子节点,且是左子树.需要建立dataNode左子树与dataNode的键连接
        changeParentQuate(dataNode, dataNode.leftChild);
        dataNode.leftChild.parent = dataNode.parent;
        updateDep(dataNode.parent);
        
    } else if (dataNode.leftChild == null && dataNode.rightChild != null) {
        // 有一个子节点,且是右子树
        changeParentQuate(dataNode, dataNode.rightChild);
        dataNode.rightChild.parent = dataNode.parent;
        updateDep(dataNode.parent);
        
    } else {
        // 有两个子节点
        if(dataNode.dep_sub>=0) {
            // 右子树长或等长
            // 判断右子树有没有左孩子
            Node xNode = dataNode.rightChild;
            if(xNode.leftChild==null) {
                // 没有左孩子就直接占用父位
                
                changeParentQuate(dataNode, xNode);
                xNode.parent=dataNode.parent;
                
                xNode.leftChild = dataNode.leftChild;
                xNode.leftChild.parent = xNode;
                
                updateDep(xNode);
            }else {
                //找到最左孩子
                while(xNode.leftChild!=null) {
                    xNode = xNode.leftChild;
                }
                //更新键
                xNode.parent.leftChild = xNode.rightChild;
                if(xNode.rightChild!=null) {
                    xNode.rightChild.parent = xNode.parent;
                }
                
                
                Node pNode = xNode.parent;
                
                changeParentQuate(dataNode, xNode);
                xNode.parent = dataNode.parent;
                xNode.leftChild = dataNode.leftChild;
                xNode.leftChild.parent = xNode;
                xNode.rightChild = dataNode.rightChild;
                dataNode.rightChild.parent = xNode;
                //更新索引 从原xNode的父节点开始
                updateDep(pNode);
            }
            
        }else {
            //左子树长
            Node xNode = dataNode.leftChild;
            if(xNode.rightChild==null) {
                //无最右节点 直接往上顶
                changeParentQuate(dataNode, xNode);
                xNode.parent = dataNode.parent;
                
                xNode.rightChild = dataNode.rightChild;
                xNode.rightChild.parent = xNode;
                
                updateDep(xNode);
                
            }else {
                while(xNode.rightChild!=null) {
                    xNode=xNode.rightChild;
                }
                Node pNode = xNode.parent;
                //更新键
                
                xNode.parent.rightChild = xNode.leftChild;
                if(xNode.leftChild!=null) {
                    xNode.leftChild.parent = xNode.parent;
                }
                
                changeParentQuate(dataNode, xNode);
                xNode.parent = dataNode.parent;
                xNode.leftChild = dataNode.leftChild;
                xNode.leftChild.parent = xNode;
                xNode.rightChild = dataNode.rightChild;
                xNode.rightChild.parent = xNode;
                updateDep(pNode);
            }
        }
    }

    return true;
}

import java.util.Stack;

public class Tree {
    public Node root = null;

    class Node {
        public int data;
        public Node leftChild;
        public Node rightChild;
        public Node parent;
        public int depth;
        public int dep_sub;

        public Node() {
            this.leftChild = null;
            this.rightChild = null;
            this.parent = null;
            this.depth = 0;
            this.dep_sub = 0;
        }

        public Node(int data) {
            this.leftChild = null;
            this.rightChild = null;
            this.parent = null;
            this.depth = 0;
            this.dep_sub = 0;
            this.data = data;
        }

        public Node(Node parent, int data) {
            this.leftChild = null;
            this.rightChild = null;
            this.depth = 0;
            this.dep_sub = 0;

            this.parent = parent;
            this.data = data;
        }

        public void setDep_sub() {
            int left = this.leftChild == null ? 0 : this.leftChild.depth;
            int right = this.rightChild == null ? 0 : this.rightChild.depth;
            // 更新深度
            this.depth = Math.max(left, right) + 1;
            // 更新深度差
            this.dep_sub = right - left;
        }

        public void setDepth() {
            int left = this.leftChild == null ? 0 : this.leftChild.depth;
            int right = this.rightChild == null ? 0 : this.rightChild.depth;
            // 更新深度
            this.depth = Math.max(left, right) + 1;
        }
    }

    // 构造方法
    public Tree() {

    }

    public Tree(Node root) {
        this.root = root;
    }

    // 功能设置判空函数
    public boolean isEmpty() {
        if (this.root == null) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public String toString() {
        String str = "";
        Node xNode = root;
        Node pNode = null;
        Stack<Node> nodes = new Stack<>();
        do {
            while (xNode != null) {
                nodes.push(xNode);
                xNode = xNode.leftChild;
            }
            pNode = nodes.pop();
            str += pNode.data + ",";
            xNode = pNode.rightChild;
        } while (!nodes.isEmpty() || xNode != null);
        return str;
    }

    /**
     * 将data插入树中
     * 
     * @param data
     */
    public void add(int data) {
        Node xNode = root;
        Node newNode = new Node(data);
        newNode.depth = 1;
        if (isEmpty()) {
            // 插入到根节点,此时root的P为null
            root = newNode;
        } else {
            while (true) {
                // 循环指导找到插入位置
                if (data < xNode.data) {
                    if (xNode.leftChild == null) {
                        xNode.leftChild = newNode;
                        newNode.parent = xNode;
                        break;
                    } else {
                        xNode = xNode.leftChild;
                    }
                } else if (data > xNode.data) {
                    if (xNode.rightChild == null) {
                        xNode.rightChild = newNode;
                        newNode.parent = xNode;
                        break;
                    } else {
                        xNode = xNode.rightChild;
                    }

                }
            }
            // 找到插入位置,更新索引
            updateDep(newNode);
        }

    }

    /**
     * 从node节点开始向上,开始更新深度和深度差,如果出现不平衡现象,进行旋转操作
     * 
     * @param node
     */
    private void updateDep(Node node) {
        if(node==null) {
            return;
        }
        node.setDep_sub();

        Node pNode = node.parent;
        if (node.dep_sub > 1) {
            // 右比左长,判断右子树中那条路出问题

            if (node.rightChild.dep_sub > 0) {
                // 右右情况,进行左旋
                leftRotate(node, node.rightChild);

            } else if (node.rightChild.dep_sub < 0) {
                // 右左情况,进行右旋,再进行左旋
                rightRotate(node.rightChild.leftChild, node.rightChild);
                leftRotate(node, node.rightChild);
            } else {
                // 右中情况 左旋
                leftRotate(node, node.rightChild);
            }

        } else if (node.dep_sub < -1) {
            if (node.leftChild.dep_sub < 0) {
                // 左左情况,进行右旋
                rightRotate(node.leftChild, node);
            } else if (node.leftChild.dep_sub > 0) {
                // 左右情况,进行左旋,再进行右旋
                leftRotate(node.leftChild, node.leftChild.rightChild);
                rightRotate(node.leftChild, node);

            } else {
                // 左中情况 右旋
                rightRotate(node.leftChild, node);
            }
        }
        updateDep(pNode);


    }

    /**
     * 左旋算法,将X旋转至Y的左子树
     * 
     * @param X
     * @param Y
     */
    private void leftRotate(Node X, Node Y) {
        if (X.parent == null) {
            // 根节点
            root = Y;
        }
        Y.parent = X.parent;
        // 将X的父亲指向Y
        changeParentQuate(X, Y);

        X.parent = Y;
        X.rightChild = Y.leftChild;
        if (X.rightChild != null) {
            X.rightChild.parent = X;
        }

        Y.leftChild = X;

        // 更新深度和深度差
        X.setDep_sub();
        Y.setDep_sub();
        System.out.print("左旋");
    }

    /**
     * 右旋算法,将Y旋转至X的右子树
     * 
     * @param X
     * @param Y
     */
    private void rightRotate(Node X, Node Y) {
        if (Y.parent == null) {
            root = X;
        }
        // 更新父节点的引用
        changeParentQuate(Y, X);
        X.parent = Y.parent;

        Y.parent = X;
        Y.leftChild = X.rightChild;
        if (Y.leftChild != null) {
            Y.leftChild.parent = Y;
        }

        X.rightChild = Y;
        // 更新深度和深度差
        Y.setDep_sub();
        X.setDep_sub();
        System.out.print("右旋");
    }

    public boolean del(int data) {
        Node dataNode = find(data);
        if (dataNode == null) {
            // 未找到
            return false;
        }
        //
        if (dataNode.leftChild == null && dataNode.rightChild == null) {
            // 若有无子节点
            changeParentQuate(dataNode, null);
            updateDep(dataNode.parent);
            
        } else if (dataNode.leftChild != null && dataNode.rightChild == null) {
            // 有一个子节点,且是左子树.需要建立dataNode左子树与dataNode的键连接
            changeParentQuate(dataNode, dataNode.leftChild);
            dataNode.leftChild.parent = dataNode.parent;
            updateDep(dataNode.parent);
            
        } else if (dataNode.leftChild == null && dataNode.rightChild != null) {
            // 有一个子节点,且是右子树
            changeParentQuate(dataNode, dataNode.rightChild);
            dataNode.rightChild.parent = dataNode.parent;
            updateDep(dataNode.parent);
            
        } else {
            // 有两个子节点
            if(dataNode.dep_sub>=0) {
                // 右子树长或等长
                // 判断右子树有没有左孩子
                Node xNode = dataNode.rightChild;
                if(xNode.leftChild==null) {
                    // 没有左孩子就直接占用父位
                    
                    changeParentQuate(dataNode, xNode);
                    xNode.parent=dataNode.parent;
                    
                    xNode.leftChild = dataNode.leftChild;
                    xNode.leftChild.parent = xNode;
                    
                    updateDep(xNode);
                }else {
                    //找到最左孩子
                    while(xNode.leftChild!=null) {
                        xNode = xNode.leftChild;
                    }
                    //更新键
                    xNode.parent.leftChild = xNode.rightChild;
                    if(xNode.rightChild!=null) {
                        xNode.rightChild.parent = xNode.parent;
                    }
                    
                    Node pNode = xNode.parent;
                    
                    changeParentQuate(dataNode, xNode);
                    xNode.parent = dataNode.parent;
                    xNode.leftChild = dataNode.leftChild;
                    xNode.leftChild.parent = xNode;
                    xNode.rightChild = dataNode.rightChild;
                    dataNode.rightChild.parent = xNode;
                    //更新索引 从原xNode的父节点开始
                    updateDep(pNode);
                }
                
            }else {
                //左子树长
                Node xNode = dataNode.leftChild;
                if(xNode.rightChild==null) {
                    //无最右节点 直接往上顶
                    changeParentQuate(dataNode, xNode);
                    xNode.parent = dataNode.parent;
                    
                    xNode.rightChild = dataNode.rightChild;
                    xNode.rightChild.parent = xNode;
                    
                    updateDep(xNode);
                    
                }else {
                    while(xNode.rightChild!=null) {
                        xNode=xNode.rightChild;
                    }
                    Node pNode = xNode.parent;
                    //更新键
                    
                    xNode.parent.rightChild = xNode.leftChild;
                    if(xNode.leftChild!=null) {
                        xNode.leftChild.parent = xNode.parent;
                    }
                    
                    changeParentQuate(dataNode, xNode);
                    xNode.parent = dataNode.parent;
                    xNode.leftChild = dataNode.leftChild;
                    xNode.leftChild.parent = xNode;
                    xNode.rightChild = dataNode.rightChild;
                    xNode.rightChild.parent = xNode;
                    updateDep(pNode);
                }
            }
        }

        return true;
    }

    /**
     * 将node父指针指向node更改为指向xNode
     * @param node
     * @param xNode
     */
    public void changeParentQuate(Node node, Node xNode) {
        if (node.parent != null) {
            if (node.parent.leftChild == node) {
                node.parent.leftChild = xNode;
            } else {
                node.parent.rightChild = xNode;
            }
        } else {
            root = xNode;
        }

    }
    /**
     * 找到data对应的节点并返回
     * @param data 数据
     * /
    public Node find(int data) {
        Node xNode = root;
        while (xNode != null) {
            if (data == xNode.data) {
                break;
            } else if (data < xNode.data) {
                // 进入左子树
                xNode = xNode.leftChild;
            } else if (data > xNode.data) {
                // 进入右子树
                xNode = xNode.rightChild;
            }
        }
        return xNode;
    }

    /**
     * 前序遍历
     * @param node 当前的节点
     * /
    public void followUp(Node node) {
        if (node != null) {
            System.out.print(node.data + ",");
            followUp(node.leftChild);
            followUp(node.rightChild);
        }

    }

}



附上测试代码

//根据先序遍历和中序遍历可以确定我们的树结构
public static void main(String[] args) {
        Tree tree = new Tree();
        int[] arr = { 12, 4, 1, 3, 7, 8, 10, 9, 2, 11, 6, 5 };
        for (int a : arr) {
            
            System.out.println("新增:" + a);
            tree.add(a);
            System.out.println("");
            System.out.println("中序遍历:" + tree.toString());
            System.out.print("前序遍历:");
            tree.followUp(tree.root);
            System.out.println();
        }
        int[] arr2 = { 1, 8, 9, 2, 6, 7 };
        for (int a : arr2) {
            System.out.println("\n删除:" + a);
            tree.del(a);
            System.out.println("中序遍历:" + tree.toString());
            System.out.print("前序遍历:");
            tree.followUp(tree.root);
        }

    }

end

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值