二叉搜索树、自平衡二叉搜索树、红黑树的Java代码实现

三颗大树

今天我们来捋以下三颗大树:ADT、AVL、RBT

ADT

二叉搜索树

插入操作

   插入操作如果要插入数字比根节点大那么向右插入,如果插入数字比根节点小则向左插入

删除操作

   如果要删除的节点是叶子节点,没有孩子那么直接删除
   如果要删除的节点又一个孩子,则直接用孩子节点来替换
   如果要删除的节点有两个孩子,用右子树的最小值或左子树的最大值;爱替换然后删除用于替换的那个节点
package com.suo.ADT;

import java.nio.BufferUnderflowException;

public class BinarySearchTree {
    private static class BinaryNode {
        BinaryNode(Integer theElement) {
            this(theElement,null,null);
        }

        public BinaryNode(Integer element, BinaryNode left, BinaryNode right) {
            this.element = element;
            this.left = left;
            this.right = right;
        }

        //节点中的数据
        Integer element;
        //左孩子
        BinaryNode left;
        //右孩子
        BinaryNode right;
    }

    //根节点
    private BinaryNode root;

    //无参构造
    public BinarySearchTree() {
        root = null;
    }

    //清空树
    public void makeEmpty() {
        root = null;
    }

    //判断树是否为空
    public boolean isEmpty() {
        return root == null;
    }

    //判断树是否含有某个节点
    public boolean contains(Integer x) {
        return contains(x,root);
    }

    //寻找这颗树的最小值
    public Integer findMin() {
        if (isEmpty()) throw  new BufferUnderflowException();
        return findMin(root).element;
    }

    //寻找这颗树的最大值
    public Integer findMax() {
        if (isEmpty()) throw  new BufferUnderflowException();
        return findMax(root).element;
    }

    //插入操作
    public void insert(Integer x) {
        root = insert(x,root);
    }

    //删除操作
    public void remove(Integer x) {
        root = remove(x,root);
    }

    //遍历操作
    public void printTree() {
        printTree(root);
        System.out.println();
    }


    /**
     * 如果相等则返回本节点,如歌比根节点小则递归左子树,如果比根节点大则递归右子树
     * @param x
     * @param t
     * @return
     */
    private boolean contains(Integer x,BinaryNode t) {
        if(t == null) return false;
        if(x == t.element) return true;
        else if(x < t.element) return contains(x,root.left);
        else return contains(x,root.right);
    }

    /**
     * 一直往左走
     * @param t
     * @return
     */
    private BinaryNode findMin(BinaryNode t) {
        if (t == null) return null;
        while(t.left != null) {
            t = t.left;
        }
        return t;
    }

    /**
     * 一直往右走
     * @param t
     * @return
     */
    private BinaryNode findMax(BinaryNode t) {
        if (t == null) return null;
        while(t.right != null) {
            t = t.right;
        }
        return t;
    }

    /**
     * 如果比根节点小往左插,反之往右插,不考虑相等的情况
     * @param x
     * @param t
     * @return
     */
    private BinaryNode insert(Integer x, BinaryNode t) {
        if (t == null) {
            return new BinaryNode(x,null,null);
        }

        if (x < t.element) {
            t.left = insert(x, t.left);
        }
        else if(x > t.element) {
            t.right = insert(x, t.right);
        }
        else {
            //do nothing
        }
        return t;
    }

    /**
     * 如果要删除的节点是叶子节点,没有孩子那么直接删除
     * 如果要删除的节点又一个孩子,则直接用孩子节点来替换
     * 如果要删除的节点有两个孩子,用右子树的最小值或左子树的最大值;爱替换然后删除用于替换的那个节点
     * @param x
     * @param t
     * @return
     */
    private BinaryNode remove(Integer x,BinaryNode t) {
        if (t == null) {
            return t;
        }

        if (x < t.element) {
            t.left = remove(x,t.left);
        }
        else if (x > t.element) {
            t.right = remove(x,t.right);
        }
        else {
            if (t.left == null && t.right == null) {
                t = null;
            }
            else if(t.left == null || t.right == null) {
                t = (t.left == null) ? t.right : t.left;
            }
            else {
                t.element = findMin(t.right).element;
                t.right = remove(t.element,t.right);
            }
        }
        return t;
    }

    private void printTree(BinaryNode t) {
        if (t == null) {
            return;
        }
        printTree(t.left);
        System.out.print(t.element + " ");
        printTree(t.right);
    }
}

AVL

AVL是自平衡二叉搜索树,相较于ADT多了一个旋转自平衡的操作

明白这一点后其实就比较简单了所有函数和ADT完全相同只是添加一个自平衡函数balance()

原先insert返回t现在返回balance(t)删除同理

自平衡

那么AVL是怎么实现自平衡的呢其实很简单一共有四种情况

LL

LL 往左子树的左子树插入导致的不平衡 右旋:
第一步:将根节点的左孩子替换此节点 AvlNode k = t.left;
第二步:将 k节点的右孩子替换根节点的左孩子 t.left = k.right;
第三步:将根节点替换为 k节点的右孩子 k.right = t;

RR

RR 往右子树的右子树插入导致的不平衡 左旋:
第一步:将根节点的右孩子替换此节点 AvlNode k = t.right;
第二步:将 k节点的左孩子替换根节点的右孩子 t.right = k.left;
第三步:将根节点替换为 k节点的左孩子 k.left = t;

LR

LR 往左子树的右子树插入导致的不平衡 先对左子树左旋,再对根右旋

RL

RL 往右子树的左子树插入导致的不平衡 先对右子树右旋,再对根左旋
package com.suo.AVL;

import java.nio.BufferUnderflowException;

public class AVL {
    private static class AvlNode {
        AvlNode(Integer theElement) {
            this(theElement,null,null);
        }

        public AvlNode(Integer element, AVL.AvlNode left, AVL.AvlNode right) {
            this.element = element;
            this.left = left;
            this.right = right;
            height = 0;
        }

        //节点中的数据
        Integer element;
        //左孩子
        AVL.AvlNode left;
        //右孩子
        AVL.AvlNode right;
        //树高
        int height;
    }

    //根节点
    private AvlNode root;

    //允许的平衡因子
    private static final int ALLOWED_IMBALANCE = 1;

    //无参构造
    public AVL() {
        root = null;
    }

    //清空树
    public void makeEmpty() {
        root = null;
    }

    //判断树是否为空
    public boolean isEmpty() {
        return root == null;
    }

    //判断树是否含有某个节点
    public boolean contains(Integer x) {
        return contains(x,root);
    }

    //寻找这颗树的最小值
    public Integer findMin() {
        if (isEmpty()) throw  new BufferUnderflowException();
        return findMin(root).element;
    }

    //寻找这颗树的最大值
    public Integer findMax() {
        if (isEmpty()) throw  new BufferUnderflowException();
        return findMax(root).element;
    }

    //插入操作
    public void insert(Integer x) {
        root = insert(x,root);
    }

    //删除操作
    public void remove(Integer x) {
        root = remove(x,root);
    }

    //遍历操作
    public void printTree() {
        printTree(root);
        System.out.println();
    }

    /**
     * 获取树高
     * @param t
     * @return
     */
    private int height(AvlNode t) {
        return  t == null ? -1 : t.height;
    }

    /**
     * 如果相等则返回本节点,如歌比根节点小则递归左子树,如果比根节点大则递归右子树
     * @param x
     * @param t
     * @return
     */
    private boolean contains(Integer x, AvlNode t) {
        if(t == null) return false;
        if(x == t.element) return true;
        else if(x < t.element) return contains(x,root.left);
        else return contains(x,root.right);
    }

    /**
     * 一直往左走
     * @param t
     * @return
     */
    private AvlNode findMin(AvlNode t) {
        if (t == null) return null;
        while(t.left != null) {
            t = t.left;
        }
        return t;
    }

    /**
     * 一直往右走
     * @param t
     * @return
     */
    private AvlNode findMax(AvlNode t) {
        if (t == null) return null;
        while(t.right != null) {
            t = t.right;
        }
        return t;
    }

    /**
     * 与ADT插入操作完全相同,只是多了一个平衡操作
     * @param x
     * @param t
     * @return
     */
    private AvlNode insert(Integer x, AvlNode t) {
        if (t == null) {
            return new AvlNode(x,null,null);
        }

        if (x < t.element) {
            t.left = insert(x, t.left);
        }
        else if(x > t.element) {
            t.right = insert(x, t.right);
        }
        else {
            //do nothing
        }
        return balance(t);
    }

    /**
     * 与ADT的删除操作完全一样,只是多了哥平衡操作
     * @param x
     * @param t
     * @return
     */
    private AvlNode remove(Integer x, AvlNode t) {
        if (t == null) {
            return t;
        }

        if (x < t.element) {
            t.left = remove(x,t.left);
        }
        else if (x > t.element) {
            t.right = remove(x,t.right);
        }
        else {
            if (t.left == null && t.right == null) {
                t = null;
            }
            else if(t.left == null || t.right == null) {
                t = (t.left == null) ? t.right : t.left;
            }
            else {
                t.element = findMin(t.right).element;
                t.right = remove(t.element,t.right);
            }
        }
        return balance(t);
    }

    /**
     * 分为四种情况
     * LL 往左子树的左子树插入导致的不平衡 右旋
     * RR 往右子树的右子树插入导致的不平衡 左旋
     * LR 往左子树的右子树插入导致的不平衡 先对左子树左旋,再对根右旋
     * RL 往右子树的左子树插入导致的不平衡 先对右子树右旋,再对根左旋
     * @param t
     * @return
     */
    private AvlNode balance(AvlNode t) {
        if(t == null) return null;
        //L
        if (height(t.left) - height(t.right) > ALLOWED_IMBALANCE) {
            //LL
            //因为删除时可能会产生等于的情况,等号加不加无所谓,加等号只是为了相等的时候采用单旋转而不是双旋转提高效率
            if (height(t.left.left) - height(t.left.right) > 0) {
                t = rotateWithLeftChild(t);
            }
            //LR
            else {
                t = doubleWithLeftChild(t);
            }
        }
        //R
        else if (height(t.right) - height(t.left) > ALLOWED_IMBALANCE){
            //RR
            //因为删除时可能会产生等于的情况,等号加不加无所谓,加等号只是为了相等的时候采用单旋转而不是双旋转提高效率
            if(height(t.right.right) - height(t.right.left) > 0) {
                t = rotateWithRightChild(t);
            }
            //RL
            else {
                t = doubleWithRightChild(t);
            }
        }
        else {
            //do nothing
        }
        t.height = Math.max(height(t.left),height(t.right)) + 1;
        return t;
    }

    /**
     * LL 往左子树的左子树插入导致的不平衡 右旋:
     * 第一步:将根节点的左孩子替换此节点 AvlNode k = t.left;
     * 第二步:将 k节点的右孩子替换根节点的左孩子 t.left = k.right;
     * 第三步:将根节点替换为 k节点的右孩子 k.right = t;
     * @param t
     * @return
     */
    private AvlNode rotateWithLeftChild(AvlNode t) {
        AvlNode k = t.left;
        t.left = k.right;
        k.right = t;
        t.height = Math.max(height(t.left),height((t.right))) + 1;
        k.height = Math.max(height(k.left),t.height) + 1;
        return k;
    }

    /**
     * RR 往右子树的右子树插入导致的不平衡 左旋:
     * 第一步:将根节点的右孩子替换此节点 AvlNode k = t.right;
     * 第二步:将 k节点的左孩子替换根节点的右孩子 t.right = k.left;
     * 第三步:将根节点替换为 k节点的左孩子 k.left = t;
     * @param t
     * @return
     */
    private AvlNode rotateWithRightChild(AvlNode t) {
        AvlNode k = t.right;
        t.right = k.left;
        k.left = t;
        t.height = Math.max(height(t.left),height((t.right))) + 1;
        k.height = Math.max(height(k.left),t.height) + 1;
        return k;
    }

    /**
     * LR 往左子树的右子树插入导致的不平衡 先对左子树左旋,再对根右旋
     * @param t
     * @return
     */
    private AvlNode doubleWithLeftChild(AvlNode t) {
        t.left = rotateWithRightChild(t.left);
        return rotateWithLeftChild(t);
    }

    /**
     * RL 往右子树的左子树插入导致的不平衡 先对右子树右旋,再对根左旋
     * @param t
     * @return
     */
    private AvlNode doubleWithRightChild(AvlNode t) {
        t.right = rotateWithLeftChild(t.right);
        return rotateWithRightChild(t);
    }

    private void printTree(AvlNode t) {
        if (t == null) {
            return;
        }
        printTree(t.left);
        System.out.print(t.element + " ");
        printTree(t.right);
    }
}

RBT

红黑树有是对AVL的改进,因为AVL需要大量的旋转导致查询提升的效率不足以弥补旋转造成的损失

红黑树相比于AVL多了个着色操作而且平衡判断条件为黑高

红黑树的调整方法
1. 如果插入节点的父节点为黑色则插入成功不需要调整
2. 如果插入节点的父节点是红色
 2.1 如果叔叔节点为红色则将父节点与叔叔节点变为黑色,爷爷节点变为红色,在将爷爷节点作为子节点,循环执行直到根节点为止,最后再将根节点变为黑色
 2.2 如果叔叔节点为黑色则与AVL相同(LL,RR,LR,RL)但需要添加一步着色操作
  着色操作:
   2.2.1. 将插入节点x的父节点p和叔叔节点u的颜色变为黑色
   2.2.2. 将x的爷爷节点变为红色
   2.2.3. 将爷爷节点看作子节点,循环执行直到根节点为止,最后再将根节点变为黑色
package com.suo.RedBlackTree;

public class RedBlackTree {
    public static final int RED = 0;
    public static final int BLACK = 1;

    //节点
    private static class RBTreeNode {
        int color;
        Integer key;
        RBTreeNode left;
        RBTreeNode right;
        RBTreeNode parent;

        public RBTreeNode(Integer key) {
            this(key,null,null,null);
        }

        public RBTreeNode(Integer key, RBTreeNode left, RBTreeNode right, RBTreeNode parent) {
            this.key = key;
            this.left = left;
            this.right = right;
            this.parent = parent;
            this.color = BLACK;
        }
    }

    //根节点
    private RBTreeNode header;

    public RedBlackTree() {
        header = new RBTreeNode(null);
    }

    public void insert(Integer key) {
        insert(header,key);
    }

    public void printTree() {
        printTree(header);
    }

    //插入方法
    private void insert(RBTreeNode root, Integer key) {
        RBTreeNode node = new RBTreeNode(key);

        insert(root,node);
    }

    //插入方法
    private void insert(RBTreeNode root, RBTreeNode node) {
        RBTreeNode x = root;//跟着x周 用于指向父节点
        RBTreeNode y = null;//查询指针

        //1. 将红黑树当成一个正常的排序树插入
        while(x != null && x.key != null) {
            y = x;
            if (node.key < x.key) {
                x = x.left;
            }
            else {
                x = x.right;
            }
        }
        node.parent = y;
        //判断当前插入节点是父节点左孩子还是有孩子
        //当y == NULL 时说明此时x为根节点
        if (y == null) {
            header = node;
        }
        else {
            if (node.key < y.key) {
                y.left = node;
            }
            else {
                y.right = node;
            }
        }
        //2. 将出插入节点设置为红色
        node.color = RED;
        //3. 将二叉搜索树修正为红黑树
        fix(node);
    }

    /**
     * 红黑树的调整方法
     * 1. 如果插入节点的父节点为黑色则插入成功不需要调整
     * 2. 如果插入节点的父节点是红色
     *  2.1 如果叔叔节点为红色则将父节点与叔叔节点变为黑色,爷爷节点变为红色,在将爷爷节点作为子节点,循环执行直到根节点为止,最后再将根节点变为黑色
     *  2.2 如果叔叔节点为黑色则与AVL相同(LL,RR,LR,RL)但需要添加一步着色操作
     *   着色操作:
     *    2.2.1. 将插入节点x的父节点p和叔叔节点u的颜色变为黑色
     *    2.2.2. 将x的爷爷节点变为红色
     *    2.2.3. 将爷爷节点看作子节点,循环执行直到根节点为止,最后再将根节点变为黑色
     * @param node
     */
    private void fix(RBTreeNode node) {
        RBTreeNode parent = null;
        RBTreeNode grandParent = null;
        //如果我的父节点存在 并且父节点颜色时红色
        while((parent = node.parent) != null && parent.color == RED) {
            grandParent = parent.parent;
            //如果父节点时祖父节点的左孩子
            if (parent == grandParent.left) {
                //条件1 叔叔节点是红色
                RBTreeNode uncle = grandParent.right;
                if (uncle != null && uncle.color == RED) {
                    uncle.color = BLACK;
                    parent.color = BLACK;
                    grandParent.color = RED;
                    node = grandParent;
                    continue;
                }
                //条件2 叔叔节点是黑色 且当前节点是右孩子
                else if (parent.right == node) {
                    //LR先左旋后右旋
                    leftRotate(parent);
                    RBTreeNode temp = parent;
                    parent = node;
                    node = temp;
                }

                parent.color = BLACK;
                grandParent.color = RED;

                //条件3 叔叔是黑色的并且当前节点是左孩子
                rightRotate(grandParent);
            }
            //如果父节点是租房节点的右孩子
            else {
                RBTreeNode uncle = grandParent.left;
                if (uncle != null && uncle.color == RED) {
                    uncle.color = BLACK;
                    parent.color = BLACK;
                    grandParent.color = RED;
                    node = grandParent;
                    continue;
                }
                //如果是黑色 并且当前结点是左孩子
                if (parent.left == node) {
                    rightRotate(parent);
                    RBTreeNode temp = parent;
                    parent = node;
                    node = temp;
                }
                parent.color = BLACK;
                grandParent.color = RED;

                //如果叔叔是黑色 并且当前结点是右孩子
                leftRotate(grandParent);
            }
        }
        header.color = BLACK;
    }

    //左旋
    public void leftRotate(RBTreeNode p) {
        // 在当前节点不为null时,才进行左旋操作
        if (p != null) {
            // 先记录p的右儿子
            RBTreeNode rightChild = p.right;

            // 1. 空出右儿子的左子树
            p.right = rightChild.left;
            // 左子树不为空,需要更新父节点
            if (rightChild.left != null) {
                rightChild.left.parent = p;
            }

            // 2. 空出节点p的父节点
            rightChild.parent = p.parent;
            // 父节点指向右儿子
            if (p.parent == null) { // 右儿子成为新的根节点
                this.header = rightChild;
            } else if (p == p.parent.left) { // 右儿子成为父节点的左儿子
                p.parent.left = rightChild;
            } else { // 右儿子成为父节点的右儿子
                p.parent.right = rightChild;
            }

            // 3. 右儿子和节点p成功会师,节点p成为左子树
            rightChild.left = p;
            p.parent = rightChild;
        }
    }

    //右旋
    public void rightRotate(RBTreeNode p) {
        if (p != null) {
            // 记录p的左儿子
            RBTreeNode leftChild = p.left;

            // 1. 空出左儿子的右子树
            p.left = leftChild.right;
            // 右子树不为空,需要更新父节点
            if (leftChild.right != null) {
                leftChild.right.parent = p;
            }

            // 2. 空出节点p的父节点
            leftChild.parent = p.parent;
            // 父节点指向左儿子
            if (p.parent == null) { // 左儿子成为整棵树根节点
                this.header = leftChild;
            } else if (p.parent.left == p) { // 左儿子成为父节点左儿子
                p.parent.left = leftChild;
            } else { // 左儿子成为父节点的右儿子
                p.parent.right = leftChild;
            }

            // 3. 顺利会师
            leftChild.right = p;
            p.parent = leftChild;
        }
    }

    //遍历
    private void printTree(RBTreeNode root) {
        if (root == null) return;
        printTree(root.left);
        System.out.print(root.key + " ");
        printTree(root.right);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值