替罪羊树.

一.节点的结构

1.左右子树的编号;
2.当前节点的值;
3.以当前节点为根的树的大小和实际大小;
4.删除标记

二.插入

前面和BST一样,后面加一个判断树是否平衡

三.删除

删除不是真正的将节点删除,而是将该节点的删除标记置为false,所以说前面有两个大小的原因就是这个,实际大小指的是删除标记没有置为false的节点数量,同样删除完后也要加一个判断树是否平衡

四.检查并判断树是否需要重构(这里就包括了检查、判断、重构和更新四个函数了)

检查重构的操作要从根节点开始查找,如果找到一个需要重构的节点,将以它为根的子树进行暴力重构
需要重构的条件:
1.当前节点的左子树或右子树的大小大于当前节点的大小乘以一个平衡因子(一般设为0.75)【注意这里就是大小,包括那些删除标记已经置为false的节点】;
2.以当前节点为根的子树内的被删除的节点(删除标记置为false)的数量大于树大小的30%了

五.重构

将当前需要重构的子树进行中序遍历拉成一条直线的序列,然后分治建树,即每次将序列中间的值构造成根节点,然后左右两边的序列分别用来构造根节点的左右子树,构造方法是一样的
这里有个注意点,就是前面那些删除标记置为false的节点在这里被丢掉,什么意思呢?重构不是要先进行中序遍历嘛,那我遍历的时候,如果遇到删除标记已经置为false的节点就不将其加入中序遍历的序列中,那不就相当于删除节点了,我都不要它参与后续的重构了
然后是对中序遍历重构的函数有一个注意点:就是找到中点m时,如果中点左边m-1的值等于中点m的值,那么就要左移m–,因为这里是希望与当前节点相同的值放到右子树

六.当前节点在树的排名以及根据排名查找节点的值(不想写文字描述了)

import java.util.ArrayList;

public class ScapegoatTree<T extends Comparable<T>> {
    private ScapegoatNode<T> root;//定义根节点
    private static final double alpha = 0.75;//树的平衡因子,一般是0.5-1,默认是0.75
    private ArrayList<ScapegoatNode<T>> inOrder = new ArrayList<>();//中序统计
    public ScapegoatTree() {
        root = null;
    }
    public void insert(T val) {
        //插入肯定是插到空节点的位置的
        ScapegoatNode<T> tmpRoot = this.root;//获取当前替罪羊树的根节点
        if(tmpRoot == null) {
            //根节点为空
            root = new ScapegoatNode<>(val);
            //checkBalance(this.root);//当完成上面的插入后,检查整棵树的平衡情况
            return;
        }
        //如果有根节点,就要找到待插入的位置,一直找到叶子节点
        ScapegoatNode<T> parent = null;//定义一个双亲节点
        int cmp = 0;
        while(tmpRoot != null) {
            tmpRoot.size++;
            tmpRoot.realSize++;
            parent = tmpRoot;//记录当前的双亲,这个是为了给后续产生的新节点提供父节点
            cmp = val.compareTo(tmpRoot.val);
            if(cmp < 0) {
                tmpRoot = tmpRoot.left;
            } else {
                tmpRoot = tmpRoot.right;
            }
        }
        //到这里就找到位置了,设置节点接上就行
        ScapegoatNode<T> e = new ScapegoatNode<>(val);
        if(cmp < 0) parent.left = e;
        else parent.right = e;

        checkBalance(this.root, null);//当完成上面的插入后,检查整棵树的平衡情况
    }
    private void removeByVal(ScapegoatNode<T> node, T val) {
        //注意:这里的删除只是将节点对应的删除标记置为false,不是真正意义上的删除节点
        if(node.exist && node.val.equals(val)) {
            //首先需要当前节点删除标记是true才去删除,不然就是已经删除了
            node.exist = false;
            node.realSize--;//实际大小一开始统计时是包括本身的,本身被删除后实际大小就减少1,而大小不变,因为还没真正删除
            checkBalance(this.root, null);
            return;
        }
        //如果当前node的值不等于待删除的值,就要往子树去删
        node.realSize--;//将以当前节点为根节点的树的实际大小-1,这里是默认一定存在待删除的值,所以代码有缺陷,后面再改
        int cmp = val.compareTo(node.val);
        if(cmp > 0) {
            //待删除值大于等于当前node的值,需要到右子树
            removeByVal(node.right, val);
        } else {
            //待删除值小于当前node的值,需要到左子树
            removeByVal(node.left, val);
        }
    }
    public void remove(T val) {
        //这是对外的接口
        removeByVal(root, val);
    }
    private void checkBalance(ScapegoatNode<T> node, ScapegoatNode<T> parent) {
        //这里是自顶向下检查平衡
        if(node == null) return;
        if(!isBalance(node)) {
            //如果当前node为根节点的树是不平衡的,就进行重建
            if(parent != null) {
                //s说明当前不是根节点
                if(parent.val.compareTo(node.val) > 0) parent.left = rebuild(node);
                update(root, node);//更新的是除开 以node为根节点的树之外size
            } else {
                //s说明当前是根节点
                root = rebuild(node);
            }
        }
        //平衡就往子树递归检查
        checkBalance(node.left, node);
        checkBalance(node.right, node);
    }

    private boolean isBalance(ScapegoatNode<T> node) {
        if(node == null || (node.left == null && node.right == null)) return true;
        int leftSize = node.left == null ? 0 : node.left.size;
        int rightSize = node.right == null ? 0 : node.right.size;
        //只要 1.左右子树其中一个的节点的大小(带已删除标记为false的那个大小) 大于 当前节点大小乘以平衡因子
        //    或者2.当前节点已删除标记为false的节点数大于当前节点大小的30%
        return !((Math.max(leftSize, rightSize) > alpha * node.size)
                || ((node.size - node.realSize) > 0.3 * node.size));//表示不平衡
    }

    private ScapegoatNode<T> rebuild(ScapegoatNode<T> node) {
        this.inOrder.clear();//先清空上一次的
        getInorder(node);
        if(inOrder.isEmpty()) {
            //如果以当前节点为根的全部节点的删除标记都置为false了,那中序遍历的序列就是空的
            //直接将当前node置为空
            return null;
        }
        //否则以当前的中序遍历序列构建新的树
        return rebuildByInorder(0, inOrder.size()-1);
    }

    private void update(ScapegoatNode<T> start, ScapegoatNode<T> node) {
        if(start == null || start == node) {
            return;
        }
        //这里是自底向上更新
        //int cmp = node.val.compareTo(start.val);
        update(start.right, node);
        update(start.left, node);
        int leftSize = start.left != null ? start.left.size : 0;
        int rightSize = start.right != null ? start.right.size : 0;
        start.size = leftSize + rightSize + 1;
    }

    public int getRank(T val) {
        //这里的排名就是找到比当前传入数字小的全部数字+1,这是输入的数不需要一定存在在树内
        ScapegoatNode<T> node = root;
        if(node == null) return 0;
        int rank = 1;
        while (node != null) {
            int cmp = val.compareTo(node.val);
            if(cmp <= 0) {
                //说明我要往左子树走,此时我的排名还不需要变换,因为我要的是比当前传入值小的,等于的只要一个,也就是一开始就设置的1
                node = node.left;
            } else {
                //说明我要往右子树走,那我的排名就要加上node(如果存在) 和 node的左子树的存在节点数量
                int leftRealSize = node.left == null ? 0 : node.left.realSize;
                rank += leftRealSize + (node.exist ? 1 : 0);
                node = node.right;
            }
        }
        return rank;
    }
    public String getNodeByRank(int rank) {
        //根据排名获取节点,只在删除标记为true的数找,不考虑那些打上删除标记的数
        ScapegoatNode<T> tmp = root;
        while (tmp != null) {
            //如果当前节点左子的存在节点和当前节点(如果存在)的存在节点刚好等于排名,那当前节点就是待查询的节点
            int leftRealSize = tmp.left == null ? 0 : tmp.left.realSize;
            if(leftRealSize + (tmp.exist ? 1 : 0) == rank) break;
            else if(leftRealSize >= rank) {
                //如果当前节点的左子节点的存在节点大于等于排名,那待查询节点一定在左子树上
                tmp = tmp.left;
            } else {
                //如果当前节点+左子树的存在节点的全部存在节点都比排名小,那就要到右子树去找
                //这个时候就要查找以右子节点为根的树,排名要减去tmp节点和tmp左子树的存活节点
                rank -= leftRealSize + (tmp.exist ? 1 : 0);
                tmp = tmp.right;
            }
        }
        if(tmp == null) return "NoExist!";
        return tmp.toString();
    }
    private ScapegoatNode<T> rebuildByInorder(int left, int right) {
        if(left == right) {
            ScapegoatNode<T> curNode = this.inOrder.get(left);
            curNode.left = null;
            curNode.right = null;
            curNode.size = 1;
            curNode.realSize = 1;
            return curNode;
        }
        int mid = left + (right - left) / 2;
        while (mid > left && inOrder.get(mid).val.equals(inOrder.get(mid - 1).val)) --mid;
        ScapegoatNode<T> curNode = inOrder.get(mid);

        //处理左子的情况
        if(mid > left) curNode.left = rebuildByInorder(left, mid - 1);
        else curNode.left = null;//如果左边没有了就将当前节点的左子树置为空
        int leftSize = curNode.left == null ? 0 : curNode.left.size;
        int leftRealSize = curNode.left == null ? 0 : curNode.left.realSize;

        //处理右子的情况
        if(mid < right) curNode.right = rebuildByInorder(mid + 1, right);//右边是一定有的,因为不平衡树的序列最少是有两个元素,此时mid的计算也是在左边
        int rightSize = curNode.right == null ? 0 : curNode.right.size;
        int rightRealSize = curNode.right == null ? 0 : curNode.right.realSize;

        //更新curNode的其他信息
        curNode.size = leftSize + rightSize + 1;
        curNode.realSize = leftRealSize + rightRealSize + 1;
        return curNode;
    }

    private void getInorder(ScapegoatNode<T> node) {
        //统计以当前node为根节点的树的中序
        if(node == null) return;
        getInorder(node.left);
        if(node.exist) {
            //注意这里就是当前node的删除标记没有置为false的才统计进去
            inOrder.add(node);
        }
        getInorder(node.right);
    }
    private void getPreorder(ScapegoatNode<T> node) {
        //统计以当前node为根节点的树的中序
        if(node == null) return;
        if(node.exist) {
            //注意这里就是当前node的删除标记没有置为false的才统计进去
            System.out.print("value:" + node.val + " exist size:" + node.size + " realSize:" + node.realSize);
            System.out.println(" left:" + (node.left != null ? node.left.val : "null") + " right:" + (node.right != null ? node.right.val : "无"));
        } else {
            System.out.print("value:" + node.val + " NoExist size:" + node.size + " realSize:" + node.realSize);
            System.out.println(" left:" + (node.left != null ? node.left.val : "null") + " right:" + (node.right != null ? node.right.val : "无"));
        }
        getPreorder(node.left);
        getPreorder(node.right);
    }
    public void prePrint() {
        //对外接口
        getPreorder(this.root);
    }
    private static class ScapegoatNode<T extends Comparable<T>> {
        T val;// 节点的值
        int size;// 以当前节点的为根的树的大小
        int realSize;// 以当前节点的为根的树的节点的实际大小(即排除删除标记为false的节点)
        boolean exist;// 删除标记
        ScapegoatNode<T> left;    // 左孩子
        ScapegoatNode<T> right;    // 右孩子
        public ScapegoatNode(T val) {
            this.val = val;
            this.size = 1;
            this.realSize = 1;
            this.exist = true;
            this.left = null;
            this.right = null;
        }

        @Override
        public String toString() {
            return "ScapegoatNode{" +
                    "val=" + val +
                    ", size=" + size +
                    ", realSize=" + realSize +
                    ", exist=" + exist +
                    ", left=" + left +
                    ", right=" + right +
                    '}';
        }
    }
}
//test函数
public class test {
    public static void main(String[] args) {
        ScapegoatTree<Integer> sg = new ScapegoatTree<>();
        //1.insert test
        sg.insert(1);
        sg.insert(2);
        sg.insert(3);
        sg.insert(4);
        sg.insert(5);
        sg.insert(6);
        sg.prePrint();

        //2.remove test
        sg.remove(4);
        sg.remove(6);
        sg.prePrint();

        //3.getRank test
        int SearchNum = 5;
        int rank = sg.getRank(SearchNum);
        System.out.println(SearchNum + "的排名:" + rank);

        //4.getNodeByRank test
        sg.remove(3);
        System.out.println(sg.getNodeByRank(4));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜以冀北

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值