package com.yc.tree;import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;/*** @author wb* @param <T>* * 替罪羊树的定义:* 1.是一种二叉排序树* 2.根节点存储了树的节点总数n和上次重建后的节点个数n上次;* 3.总能保持宽松的α高度平衡,即h<= hα + 1;* * 对于排序二叉树的根节点:* α的高度平衡 :h <= hα (其中 hα = log(1/α)n = -(logn/logα)),也就是(AVL,红黑树)* 宽松α的高度平衡 :h <= hα + 1(其中hα = log(1/α)n = -(logn/logα))* α权重平衡:(n左  <= α*n) && (n右  <= α*n)* * 当n一定时:* α越小,树越稠密,插入效率越低,查询效率越高* α越大,树越稀疏,插入效率越高,查询效率越低* * 从上面的三个式子可以看出:*    满足 α权重平衡的树一定满足 α的高度平衡;满足 α的高度平衡的树一定满足宽松 α的高度平衡;*/
public class ScapegoatTree <T extends Comparable<T>>{private class Node{T data;Node parent;Node left;Node right;public Node(T data, Node parent, Node left, Node right){this.data = data;this.parent = parent;this.left = left;this.right = right;}public String toString(){return "[data="+data+"]";}}//根节点private Node root;//上次修改的节点数private int lastModifyNodeCount = 0;//节点总数好像也要记private int NodeCount = 0;//阀值αprivate static final double threshold = 0.57;public ScapegoatTree(){root = null;}public ScapegoatTree(T data){root = new Node(data, null, null, null);NodeCount ++;}public Node root(){return this.root;}/*** 将指定数据元素data添加到该替罪羊树* @param data:指定数据元素*/public void add(T data){if(root == null){root = new Node(data, null, null, null);NodeCount ++;}else{//求得带加入节点的父节点Node parent = parent(data);if(parent != null){ //没有相同的数据元素节点int result = data.compareTo(parent.data);Node node = new Node(data, parent, null, null);if(result > 0){parent.right = node;}else{parent.left = node;}NodeCount ++;/*double ha = (-1) * Math.log10(NodeCount)/Math.log10(threshold);int deep_1 = deep_1(root); if(deep_1 <= ha){//满足阿尔法的高度平衡return;}else{*///System.out.println("打破了树的阿尔法高度平衡:" + node.data);//这个地方有两种方案找出替罪羊节点。//1.从新插入的节点入手一层一层向上回溯直到找到第一个不满足α权重平衡的节点,即!((n左  <= α*n) && (n右  <= α*n))//但是这里需要记录每个节点下面的节点总数,这就需要在Node内部类中增加一个size域来保存它,从而增加了内存开销,也就是空间换时间吧.//2.从网上找的:从插入位置开始一层一层往上回溯的时候,对于每一层都进行一次判断h(v) > log(1/alpha )(size(tree)),//一直找到最后一层不满足该条件的层序号(也就是从根开始的第一层不满足该条件的层序号,或者说是最接近根的一层,当然也包括根本身),//然后从该层开始重构以该层为根的子树。这种方法的缺点是每次回溯比较都要计算一次h(v)。也就是遍历树了,虽然不一定是遍历整个树。但还是耗时啊。//上面式子的size(tree)我有两种理解://    ①size(tree)指的是整棵树的总节点数,如果是这样,那么它的替罪羊节点永远都会是根节点。原因如下://   我们假设存在一点满足   h某节点  > log(1/alpha )(size(tree)),那么它的根节点也一定满足//               h根 > h某节点  > log(1/alpha )(size(tree));//   又由于它是在找一个更接近根的替罪羊,所以替罪羊必定为根。这种做法也就是你不停地往树中插入元素时,当达到一定程度(h根 > log(1/alpha )(size(tree))),//  就要对整棵树进行重构。//   ②size(tree)指的是往上一层一层回溯时以某层为根节点的节点总数,如果是这样的话,那么它的替罪羊节点可能为根,也可能不为根。//  这种理解就需要记录每个节点下面的节点总数,这就需要在Node内部类中增加一个size域来保存以它为根的节点总数.//   我们假设存在一点刚好是根节点的右子节点满足  h右  > log(1/alpha )(size(tree右))//                                h根 >= h右 + 1//                             h根 < log(1/alpha )(size(tree右)+size(tree左) + 1)//  结合上面三个式子可以看出: log(1/alpha )(size(tree右)) + 1 < h右 + 1 <= h根 < log(1/alpha )(size(tree右)+size(tree左) + 1);//    即log(1/alpha)(size(tree右)*(1/alpha)) < log(1/alpha )(size(tree右)+size(tree左) + 1);//   又由于alpha的取值在[0.5, 1],所以 上式就变成了 size(tree右)+size(tree右) < size(tree右)+size(tree左) + 1;// 即: size(tree右)<size(tree左)+1;很明显这种情况是可能出现的。////   这里我采用第二种方案的第一种理解:因为我坚信替罪羊树定义的第二点:根节点存储了树的节点总数n和上次重建后的节点个数n上次;//    即除根节点外,其他节点不可以有除data、parent、left、right域的其他域。//找到替罪羊节点Node sgNode = scapegoatNode(node);if(sgNode == null){return;}else{System.out.println("打破了树的阿尔法高度平衡:" + node.data);//存储替罪羊节点的父节点Node prenodelink = sgNode.parent;//暴力执法后的根节点Node succnodelink = iDoNotKnow(sgNode);//保存上次重构的节点总数lastModifyNodeCount = NodeCount;if(prenodelink == null){ //替罪羊节点是根节点root = succnodelink;succnodelink.parent = null;}else{       //替罪羊节点不是根节点 。(ps:其实这里不用写,程序永远也进不来,因为我的做法导致了替罪羊节点必定是根节点) succnodelink.parent = prenodelink;if(sgNode == prenodelink.left){prenodelink.left = succnodelink;}else{prenodelink.right = succnodelink;}}}}}}private Node iDoNotKnow(Node node){List<Node> nodes = clap(node);pickUp(nodes, 0, nodes.size() / 2, nodes.size() - 1);return nodes.get(nodes.size() / 2);}//拎起来private void pickUp(List<Node> nodes, int lstart, int index, int rend){if(nodes != null){Node current = nodes.get(index);current.left = current.right = null;  //这里的current.parent 不能赋为null//index索引处节点的左子节点if(lstart <= index - 1){Node lnext = nodes.get( (index + lstart) / 2);current.left = lnext;lnext.parent = current;//这个很重要,当某节点为重构树中的叶子节点时,一定要把它的左、右子节点赋为null;不然遍历的时候无穷无尽,导致stack异常if(lstart == index - 1){ lnext.left = lnext.right = null;}pickUp(nodes, lstart, (index + lstart) / 2, index - 1);}//index索引处节点的右子节点if(index + 1 <= rend){Node rnext = nodes.get((index + rend ) / 2 + 1);current.right = rnext;rnext.parent = current;if(rend == index + 1){rnext.left = rnext.right = null;}pickUp(nodes, index + 1, (index + rend ) / 2 + 1, rend);}}}//暴力拍平 = 中序遍历private List<Node> clap(Node node){List<Node> nodes = new ArrayList<Node>();//Deque<Node> deque = new ArrayDeque<Node>();if(node.left != null){nodes.addAll(clap(node.left));}nodes.add(node);if(node.right != null){nodes.addAll(clap(node.right));}return nodes;}//找替罪羊private Node scapegoatNode(Node node){Node sgNode = null;Node current = node;double ha;int deep_1;while(current != null){ha = (-1) * Math.log10(NodeCount)/Math.log10(threshold); //替罪羊永远是根,其实没有必要回溯,只要对根进行判断。deep_1 = deep_1(current); if(deep_1 > ha){sgNode = current;current = current.parent;}else{current = current.parent;}}return sgNode;}//根据指定数据元素找他的父节点private Node parent(T data){/*if(root == null){ //就这个程序而言没必要判断吧,因为前面已经判断了一次。return null;}else{*/Node current = root;Node parent = current;int result = 0;while(current != null){parent = current;result = data.compareTo(current.data);if(result > 0){current = current.right;}else if(result < 0){current = current.left;}else{ //这里什么也不写,这就导致了如果要加入的数据元素跟树中已存在的节点data域相同时,//不会加入该替罪羊树。return null;}}return parent;}//高度hprivate int deep_1(Node node){return deep(node) - 1;}//求以某节点为根的高度 public int deep(Node node){if(node != null){if(node.left == null && node.right == null){return 1;}int leftDeep = deep(node.left);int rightDeep = deep(node.right);return leftDeep >= rightDeep ? leftDeep + 1 : rightDeep + 1;}return 0;}/*** 删除数据元素为data的节点* @param data* * 对于替罪羊树的删除有两种做法:* 1、向排序二叉树一样删除,然后进行一次     删除某节点后的节点总数n < 阀值α * 上次重建树的节点个数 lastModityNodeCount 判断。* 若满足,再次重构树。* 2.伪删除:替罪羊树的删除节点并不是真正的删除,而是惰性删除(即给节点增加一个已经删除的标记,删除后的节点与普通节点无异,只是不参与查找操作而已)* 很显然这种做法又要在Node内部类中新增加一个标记域。浪费空间。* * 这里我是第一种做法*/public void remove(T data){Node node = find(data);if(node == null){return;}else{if(node.left == null && node.right == null){ //node为叶子节点if(node.parent == null){//node是根root = null;}else{if(node == node.parent.left){node.parent.left = null;}else{node.parent.right = null;}node.parent = null;}NodeCount --;}else if(node.left != null && node.right == null){//node只有左子树if(node.parent == null){//node是根root = node.left;node.left.parent = null;node.left = null;}else{node.left.parent = node.parent;if(node == node.parent.left){node.parent.left = node.left;}else{node.parent.right = node.left;}node.parent = node.left = null;}NodeCount --;}else if(node.right != null && node.left == null){ //node只有右子节点if(node.parent == null){//node是根root = node.right;node.right.parent = null;node.right = null;}else{node.right.parent = node.parent;if(node == node.parent.left){node.parent.left = node.right;}else{node.parent.right = node.right;}node.parent = node.right = null;}NodeCount --;    }else{//找到用于替换的后继节点Node succ = succNode(node);//替换数据node.data = succ.data;//删除替换的后继节点if(succ.parent == node){node.right = succ.right;if(succ.right != null){succ.right.parent = node;}succ.parent = succ.right = null;}else{succ.parent.left = succ.right;if(succ.right != null){succ.right.parent = succ.parent;}succ.parent = succ.right = null;}NodeCount --;}//删除过后看是否要重建整个树lastModifyNodeCount = lastModifyNodeCount == 0 ? NodeCount : lastModifyNodeCount;if(NodeCount < threshold*lastModifyNodeCount){Node newRoot = iDoNotKnow(root);root = newRoot;newRoot.parent = null;lastModifyNodeCount = NodeCount;}}}//public Node find(T data){Node current = root;int result;while(current != null){result = data.compareTo(current.data);if(result == 0){return current;}else if(result > 0){current = current.right;}else{current = current.left;}}return null;}/*** 某节点的后继节点(用于删除左、右子树不为空的节点)* @param node:某节点* @return:后继节点*/public Node succNode(Node node){Node succ = null;int result;Node current = node;while(current != null){result = node.data.compareTo(current.data);if(result < 0){succ = current;current = current.left;}else{current = current.right;}}return succ;}//广度优先遍历public List<Node> breadthFirstSearch(){return cBreadthFirstSearch(root);}private List<Node> cBreadthFirstSearch(Node node) {List<Node> nodes = new ArrayList<Node>();Deque<Node> deque = new ArrayDeque<Node>();if(node != null){deque.offer(node);}while(!deque.isEmpty()){Node first = deque.poll();nodes.add(first);if(first.left != null){deque.offer(first.left);}if(first.right != null){deque.offer(first.right);}}return nodes;}public static void main(String[] args) {ScapegoatTree<Integer> tree = new ScapegoatTree<Integer>();tree.add(40);System.out.println("加入40后:"+tree.breadthFirstSearch());tree.remove(40);System.out.println("删除40后:"+tree.breadthFirstSearch());System.out.println();tree.add(10);tree.add(8);tree.add(12);tree.add(7);tree.add(9);tree.add(11);tree.add(14);tree.add(16);System.out.println("加入16后:"+tree.breadthFirstSearch());tree.add(18);System.out.println("加入18后:"+tree.breadthFirstSearch());System.out.println();// 9*0.57 = 5.13tree.remove(14);tree.remove(16);System.out.println("删除14,16后:"+tree.breadthFirstSearch());tree.remove(12);System.out.println("删除12后:"+tree.breadthFirstSearch());tree.remove(18);System.out.println("删除18后:"+tree.breadthFirstSearch());}
}

测试结果为:

加入40后:[[data=40]]
删除40后:[]加入16后:[[data=10], [data=8], [data=12], [data=7], [data=9], [data=11], [data=14], [data=16]]
打破了树的阿尔法高度平衡:18
加入18后:[[data=11], [data=9], [data=16], [data=8], [data=10], [data=14], [data=18], [data=7], [data=12]]删除14,16后:[[data=11], [data=9], [data=18], [data=8], [data=10], [data=12], [data=7]]
删除12后:[[data=11], [data=9], [data=18], [data=8], [data=10], [data=7]]
删除18后:[[data=9], [data=8], [data=11], [data=7], [data=10]]

下面是我给出的参考资料地址:

[Scapegoat Tree] & BZOJ3224

下面是百度文库里的两个地址:

替罪羊树

平衡树

Java基础 - 替罪羊树(Scapegoat Tree)相关推荐

  1. 数据结构与算法(C++)– 树(Tree)

    数据结构与算法(C++)– 树(Tree) 1.树的基础知识 树(tree): 一些节点的集合,可以为空集 子树(sub tree): 树的子集 根(root): 树的第一个节点 孩子和父亲(Chil ...

  2. java 实现部门树_(java实现)哈夫曼(Huffman)树编码(自编压缩项目基础)

    哈夫曼树 给定 n 个权值作为 n 个叶子结点,构造一棵二叉树, 若该树的带权路径长度(wpl) 达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree), 也叫霍夫曼树. 哈 ...

  3. Java 基础 第3阶段:高级应用——尚硅谷学习笔记(含面试题) 2023年

    Java 基础 第 3 阶段:高级应用--尚硅谷学习笔记(含面试题) 2023 年 Java 基础 第 3 阶段:高级应用--尚硅谷学习笔记(含面试题) 2023 年 第 9 章 异常处理 9.1 异 ...

  4. Java基础-JAVA中常见的数据结构介绍

    Java基础-JAVA中常见的数据结构介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是数据结构 答:数据结构是指数据存储的组织方式.大致上分为线性表.栈(Stack) ...

  5. 100道Java基础面试题收集整理(附答案)

    不积跬步无以至千里,这里会不断收集和更新Java基础相关的面试题,目前已收集100题. 1.什么是B/S架构?什么是C/S架构 B/S(Browser/Server),浏览器/服务器程序 C/S(Cl ...

  6. 判断一个java对象中的属性是否都未赋值_100道Java基础面试题(一)

    100道Java基础面试题(一)未来大家将步入职场,面试的时候面试官还会问大家一些Java相关的问题.小编整理出100道非常实用的面试题目,可以帮助双体的小伙伴应对面试,我们一起来看看都有哪些吧! 1 ...

  7. java基础篇---第一天

    今日开始在心中正式开始在培训班开始培训.一下是在培训的过程中发现自己在自学过的过程中发现的问题.这篇是java基础篇. 第一天 : 1)配置java环境变量 1.在系统变量中新建JAVA_HOME:j ...

  8. JAVA基础知识学习全覆盖

    文章目录 一.JAVA基础知识 1.一些基本概念 1.Stringbuffer 2.局部变量成员变量 3.反射机制 4.protect 5.pow(x,y) 6.final ,finally,fina ...

  9. JAVA基础-06.集合-14.【List、Set】

    01_数据结构_栈&队列 02_数据结构_数组 03_数据结构_链表 04_数据结构_红黑树 05_HashSet集合存储数据的结构(哈希表) 06_Set集合存储元素不重复的原理 day03 ...

  10. Java基础 常见数据结构与算法 项目总结

    Java基础 1 Java基础必知必会 1.1 Java语言有哪些特点? 面向对象(封装,继承,多态): 平台无关性,平台无关性的具体表现在于,Java 是"一次编写,到处运行(Write ...

最新文章

  1. 基于法律罪行知识图谱的智能预判与客服问答
  2. Android访问数据库(SQL Server 和 MySQL)
  3. 【IKExpression】IKExpressionV2.0简易表达式解析器
  4. 用一句位运算判断两个整数的大小并返回较大者
  5. Java基础:JDBC
  6. yb3防爆电机型号含义_【产品信息】防爆充电机
  7. wmware 安装xp系统虚拟机
  8. 精选的 Go 框架,库和软件的精选清单
  9. 抖音算法推荐机制详解(科普向)
  10. 2022-2027年中国血液制品行业市场深度分析及投资战略规划报告
  11. 中通开放平台简介——连锁门店解决方案
  12. 基于51单片机十字路口交通信号灯(启动按键+绿灯同亮报警)
  13. Win11--将右键菜单改回Win10(展开菜单)
  14. 关于cv2.cvtColor(im, cv2.COLOR_RGB2BGR)的一点细节
  15. 帝国CMS数据字典 Ctrl+F查询
  16. 《鸟哥的Linux私房菜》精要 持续更新。。。
  17. 一周内咸鱼疯转2.4W次,最终被所有大厂封杀
  18. [国家集训队2012]电子对撞机nbsp;解题…
  19. cookie模拟登陆爬取药智网中药材数据库数据
  20. Hive分析函数之ntile、排名函数学习

热门文章

  1. php layim 图片正则替换,用正则表达式批量替换图片路径方法
  2. 3.29 段落文字的属性设置 [原创Ps教程]
  3. 苹果计算机单位换算,单位转换器 - 单位换算
  4. 记成功坚持学习英语45天截点—Darren
  5. latex如何打空格
  6. 三星n8000平板_三星n8000拆机方法介绍【图解】
  7. Java实验9 矩形类的定义与封装
  8. MySQL - 实战 棋牌游戏数据库开发
  9. C语言网络编程实战之线上五子棋游戏(二)
  10. 软工网络15团队作业8——Beta阶段冲刺合集