有序表(中):Size Balanced Tree(SBT/SB树)

news2024/10/1 15:13:23

1、SB树简介

本质上是一棵二叉搜索树,SB树全称 Size Balanced Tree,顾名思义,这是一棵通过大小(Size)域来维持平衡的二叉搜索树。

它不仅支持简单的二叉搜索树操作,也支持 Select 和 Rank。

定义一下Size的概念,对于树上某个节点而言,它的Size指的是以它为树根的子树当中节点的数量

SB树的平衡条件:对于SBT的每个节点,每棵子树的大小不小于其兄弟的子树大小。

图示: s [ X ] s[X] s[X] 表示以 X X X 为根的子树当中节点的数量。

在这里插入图片描述

在之前讲解的AVL树中平衡因子是高度,所以每个节点需要维护高度,那么在SB树中平衡因子是节点个数,所以每个节点需要维护节点个数size。

SB树的平衡条件没有AVL树那么严苛,但是也相当平衡。因为SB树的平衡条件限制,所以对于任意节点来说,它左右两棵子树的规模差距都不会到2倍以上,所以可以认为整棵树的高度几乎也就是 l o g N logN logN 的水准,这个 N N N 是树中的节点的数量最大值。

SB树的平衡性相对于AVL树的平衡性来说,进行了一定程度的阉割。

2、SB树的失衡类型

和AVL树一样,SB树的失衡类型也有四种,但是这四种的意义有所不同。

- LL型:父节点不失衡,
设计一个函数 SBTNode maintain(SBTNode node),作用是调整失衡的SB树,注意传入的是失衡节点的父节点。

LL型失衡的调整:
在这里插入图片描述
LR型失衡的调整:
在这里插入图片描述

注意和AVL树失衡调整的区别:AVL树发生了LL型失衡则只需要对失衡节点进行一次右旋即可,而SB树发生了LL型失衡需要将失衡节点的父节点进行右旋,然后对右旋后的树中孩子发生变化的节点再进行递归调整。

SB树的RR型失衡和LL型失衡是对称的,所以SB树的RR型失衡也是将失衡节点的父节点进行左旋,然后对左旋后的树中孩子发生变化的节点再进行递归调整。

SB树的LL型、LR型、RR型、RL型失衡的调整动作和AVL树是一样的,不同点在于在调整完后还要审视哪些节点的孩子发生了变化,对于孩子发生变化的节点要做后续的递归调整。

之所以在旋转后要进行递归调整,是因为在旋转后,孩子发生变化的节点的叔叔和侄子关系发生了变化,对于新的叔侄关系需要重新检查是否满足SB树的平衡条件。

和AVL树一样,在新增节点的时候,从新增的节点开始到头结点沿途受影响的节点全部都要调用 maintain 函数进行调整。

SB树在删除节点的时候不进行平衡调整,而是在新增节点的时候才进行平衡调整。但是整个平衡调整是递归行为,平衡调整会传递,所以即使在删除节点的时候不进行平衡调整,只在新增节点的时候调整,依然能把这棵树调整得平衡。这也是为什么建议用Size Balanced Tree去实现有序表,因为它省掉了删除节点时的调整动作。

比较极端的,SB树最大有 N N N 个节点,它的高度最高也就是 l o g N logN logN,如果一直只进行删除操作,那么树有可能变成链状而不平衡,但是没关系,这棵树依然是 l o g N logN logN 性能,因为这个 N N N 就是整棵树能冲到最大节点数量的值,后续如果再加入新的节点,又会进行平衡调整,树又会变得平衡。而AVL树没有递归行为。

3、SB树代码实现

public class SizeBalancedTreeMap {
	//SB树节点定义
	public static class SBTNode<K extends Comparable<K>, V> {
		public K key;
		public V value;
		public SBTNode<K, V> l;
		public SBTNode<K, V> r;
		public int size; // 不同的key的数量,节点数量

		public SBTNode(K key, V value) {
			this.key = key;
			this.value = value;
			size = 1;
		}
	}

	public static class SizeBalancedTreeMap<K extends Comparable<K>, V> {
		private SBTNode<K, V> root;

		private SBTNode<K, V> rightRotate(SBTNode<K, V> cur) {
			SBTNode<K, V> leftNode = cur.l;
			cur.l = leftNode.r;
			leftNode.r = cur;
			leftNode.size = cur.size;
			cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1;
			return leftNode;
		}

		private SBTNode<K, V> leftRotate(SBTNode<K, V> cur) {
			SBTNode<K, V> rightNode = cur.r;
			cur.r = rightNode.l;
			rightNode.l = cur;
			rightNode.size = cur.size;
			cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1;
			return rightNode;
		}
		
		//调整平衡:cur这棵树平衡的调整(基于节点数量size)
		private SBTNode<K, V> maintain(SBTNode<K, V> cur) {
			if (cur == null) {
				return null;
			}
			int leftSize = cur.l != null ? cur.l.size : 0;
			int leftLeftSize = cur.l != null && cur.l.l != null ? cur.l.l.size : 0;
			int leftRightSize = cur.l != null && cur.l.r != null ? cur.l.r.size : 0;
			int rightSize = cur.r != null ? cur.r.size : 0;
			int rightLeftSize = cur.r != null && cur.r.l != null ? cur.r.l.size : 0;
			int rightRightSize = cur.r != null && cur.r.r != null ? cur.r.r.size : 0;
			if (leftLeftSize > rightSize) { //LL型失衡
				cur = rightRotate(cur); //先进行右旋
				cur.r = maintain(cur.r); //递归调用maintain,对右旋后的右孩子进行调整
				cur = maintain(cur); //递归调用maintain,对右旋后的根进行调整
			} else if (leftRightSize > rightSize) { //LR型失衡,有3个节点的孩子发生了变化,都要递归调用maintain函数进行调整
				cur.l = leftRotate(cur.l);
				cur = rightRotate(cur);
				cur.l = maintain(cur.l);
				cur.r = maintain(cur.r);
				cur = maintain(cur);
			} else if (rightRightSize > leftSize) {
				cur = leftRotate(cur);
				cur.l = maintain(cur.l);
				cur = maintain(cur);
			} else if (rightLeftSize > leftSize) {
				cur.r = rightRotate(cur.r);
				cur = leftRotate(cur);
				cur.l = maintain(cur.l);
				cur.r = maintain(cur.r);
				cur = maintain(cur);
			}
			return cur;
		}
		
		// <=key,离key最近的节点
		private SBTNode<K, V> findLastIndex(K key) {
			SBTNode<K, V> pre = root;
			SBTNode<K, V> cur = root;
			while (cur != null) {
				pre = cur;
				if (key.compareTo(cur.key) == 0) {
					break;
				} else if (key.compareTo(cur.key) < 0) {
					cur = cur.l;
				} else {
					cur = cur.r;
				}
			}
			return pre;
		}
		
		//>=key,离key最近的节点
		private SBTNode<K, V> findLastNoSmallIndex(K key) {
			SBTNode<K, V> ans = null;
			SBTNode<K, V> cur = root;
			while (cur != null) {
				if (key.compareTo(cur.key) == 0) {
					ans = cur;
					break;
				} else if (key.compareTo(cur.key) < 0) {
					ans = cur;
					cur = cur.l;
				} else {
					cur = cur.r;
				}
			}
			return ans;
		}

		private SBTNode<K, V> findLastNoBigIndex(K key) {
			SBTNode<K, V> ans = null;
			SBTNode<K, V> cur = root;
			while (cur != null) {
				if (key.compareTo(cur.key) == 0) {
					ans = cur;
					break;
				} else if (key.compareTo(cur.key) < 0) {
					cur = cur.l;
				} else {
					ans = cur;
					cur = cur.r;
				}
			}
			return ans;
		}

		// 现在,以cur为头的树上,新增,加(key, value)这样的记录
		// 加完之后,会对cur做检查,该调整调整
		// 返回,调整完之后,整棵树的新头部
		private SBTNode<K, V> add(SBTNode<K, V> cur, K key, V value) {
			if (cur == null) {
				return new SBTNode<K, V>(key, value);
			} else {
				cur.size++;
				if (key.compareTo(cur.key) < 0) {
					cur.l = add(cur.l, key, value);
				} else {
					cur.r = add(cur.r, key, value);
				}
				return maintain(cur);
			}
		}

		// 在cur这棵树上,删掉key所代表的节点
		// 返回cur这棵树的新头部
		private SBTNode<K, V> delete(SBTNode<K, V> cur, K key) {
			cur.size--;
			if (key.compareTo(cur.key) > 0) {
				cur.r = delete(cur.r, key);
			} else if (key.compareTo(cur.key) < 0) {
				cur.l = delete(cur.l, key);
			} else { // 当前要删掉cur
				if (cur.l == null && cur.r == null) {
					// free cur memory -> C++
					cur = null;
				} else if (cur.l == null && cur.r != null) {
					// free cur memory -> C++
					cur = cur.r;
				} else if (cur.l != null && cur.r == null) {
					// free cur memory -> C++
					cur = cur.l;
				} else { // 有左有右
					SBTNode<K, V> pre = null;
					SBTNode<K, V> des = cur.r;
					des.size--;
					while (des.l != null) {
						pre = des;
						des = des.l;
						des.size--;
					}
					if (pre != null) {
						pre.l = des.r;
						des.r = cur.r;
					}
					des.l = cur.l;
					des.size = des.l.size + (des.r == null ? 0 : des.r.size) + 1;
					// free cur memory -> C++
					cur = des;
				}
			}
			//下面这行代码加与不加都可以,通常是在删除的时候不进行调整,反正add的时候会进行调整
			// cur = maintain(cur);
			return cur;
		}

		private SBTNode<K, V> getIndex(SBTNode<K, V> cur, int kth) {
			if (kth == (cur.l != null ? cur.l.size : 0) + 1) {
				return cur;
			} else if (kth <= (cur.l != null ? cur.l.size : 0)) {
				return getIndex(cur.l, kth);
			} else {
				return getIndex(cur.r, kth - (cur.l != null ? cur.l.size : 0) - 1);
			}
		}

		public int size() {
			return root == null ? 0 : root.size;
		}

		public boolean containsKey(K key) {
			if (key == null) {
				throw new RuntimeException("invalid parameter.");
			}
			SBTNode<K, V> lastNode = findLastIndex(key);
			return lastNode != null && key.compareTo(lastNode.key) == 0 ? true : false;
		}

		// (key,value) put -> 有序表 新增、改value
		public void put(K key, V value) {
			if (key == null) {
				throw new RuntimeException("invalid parameter.");
			}
			SBTNode<K, V> lastNode = findLastIndex(key);
			if (lastNode != null && key.compareTo(lastNode.key) == 0) {
				lastNode.value = value;
			} else {
				root = add(root, key, value);
			}
		}

		public void remove(K key) {
			if (key == null) {
				throw new RuntimeException("invalid parameter.");
			}
			if (containsKey(key)) {
				root = delete(root, key);
			}
		}

		public K getIndexKey(int index) {
			if (index < 0 || index >= this.size()) {
				throw new RuntimeException("invalid parameter.");
			}
			return getIndex(root, index + 1).key;
		}

		public V getIndexValue(int index) {
			if (index < 0 || index >= this.size()) {
				throw new RuntimeException("invalid parameter.");
			}
			return getIndex(root, index + 1).value;
		}

		public V get(K key) {
			if (key == null) {
				throw new RuntimeException("invalid parameter.");
			}
			SBTNode<K, V> lastNode = findLastIndex(key);
			if (lastNode != null && key.compareTo(lastNode.key) == 0) {
				return lastNode.value;
			} else {
				return null;
			}
		}

		public K firstKey() {
			if (root == null) {
				return null;
			}
			SBTNode<K, V> cur = root;
			while (cur.l != null) {
				cur = cur.l;
			}
			return cur.key;
		}

		public K lastKey() {
			if (root == null) {
				return null;
			}
			SBTNode<K, V> cur = root;
			while (cur.r != null) {
				cur = cur.r;
			}
			return cur.key;
		}

		public K floorKey(K key) {
			if (key == null) {
				throw new RuntimeException("invalid parameter.");
			}
			SBTNode<K, V> lastNoBigNode = findLastNoBigIndex(key);
			return lastNoBigNode == null ? null : lastNoBigNode.key;
		}

		public K ceilingKey(K key) {
			if (key == null) {
				throw new RuntimeException("invalid parameter.");
			}
			SBTNode<K, V> lastNoSmallNode = findLastNoSmallIndex(key);
			return lastNoSmallNode == null ? null : lastNoSmallNode.key;
		}

	}

	// for test
	public static void printAll(SBTNode<String, Integer> head) {
		System.out.println("Binary Tree:");
		printInOrder(head, 0, "H", 17);
		System.out.println();
	}

	// for test
	public static void printInOrder(SBTNode<String, Integer> head, int height, String to, int len) {
		if (head == null) {
			return;
		}
		printInOrder(head.r, height + 1, "v", len);
		String val = to + "(" + head.key + "," + head.value + ")" + to;
		int lenM = val.length();
		int lenL = (len - lenM) / 2;
		int lenR = len - lenM - lenL;
		val = getSpace(lenL) + val + getSpace(lenR);
		System.out.println(getSpace(height * len) + val);
		printInOrder(head.l, height + 1, "^", len);
	}

	// for test
	public static String getSpace(int num) {
		String space = " ";
		StringBuffer buf = new StringBuffer("");
		for (int i = 0; i < num; i++) {
			buf.append(space);
		}
		return buf.toString();
	}

	public static void main(String[] args) {
		SizeBalancedTreeMap<String, Integer> sbt = new SizeBalancedTreeMap<String, Integer>();
		sbt.put("d", 4);
		sbt.put("c", 3);
		sbt.put("a", 1);
		sbt.put("b", 2);
		// sbt.put("e", 5);
		sbt.put("g", 7);
		sbt.put("f", 6);
		sbt.put("h", 8);
		sbt.put("i", 9);
		sbt.put("a", 111);
		System.out.println(sbt.get("a"));
		sbt.put("a", 1);
		System.out.println(sbt.get("a"));
		for (int i = 0; i < sbt.size(); i++) {
			System.out.println(sbt.getIndexKey(i) + " , " + sbt.getIndexValue(i));
		}
		printAll(sbt.root);
		System.out.println(sbt.firstKey());
		System.out.println(sbt.lastKey());
		System.out.println(sbt.floorKey("g"));
		System.out.println(sbt.ceilingKey("g"));
		System.out.println(sbt.floorKey("e"));
		System.out.println(sbt.ceilingKey("e"));
		System.out.println(sbt.floorKey(""));
		System.out.println(sbt.ceilingKey(""));
		System.out.println(sbt.floorKey("j"));
		System.out.println(sbt.ceilingKey("j"));
		sbt.remove("d");
		printAll(sbt.root);
		sbt.remove("f");
		printAll(sbt.root);

	}
}

4、SB树存在的意义

Java系统自带的有序表TreeMap、TreeSet底层是使用红黑树实现的,它们提供了一些功能,但是如果需要“查找 ≤ K \le K K 的值有几个”这种功能就需要改写有序表,而这些系统没有的功能的改写仅仅是基于二叉搜索树,和SB树、AVL树没有人任何关系,为了更加高效,一般需要用到平衡型,而通常使用Size Balanced Tree 改写是最简单的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/348314.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

每天一道大厂SQL题【Day10】电商分组TopK实战

每天一道大厂SQL题【Day10】电商分组TopK实战 大家好&#xff0c;我是Maynor。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深大数据选手&#xff0c;深知SQL重要性&#xff0c;接下来我准备用100天时间&#xff0c;基于大数据岗面试中的经典SQL题&…

Linux入门篇(二)

Linux前言链接文件符号链接&#xff08;软链接&#xff09;硬链接shellshell 的类型shell的父子关系理解外部命令和内建命令外部命令内建命令Linux环境变量PATH环境变量前言 在这一章&#xff0c;我对Linux中有关shell较为深入的理解和环境变量方面知识的一个记录。同时&#x…

PBR工作流实现与对比

工作流实现工作流中的核心内容便是贴图&#xff0c;不论是UE4还是Unity都支持将PBR的参数以贴图的形式传入引擎&#xff0c;我们可以根据一个物体同一mesh或不同mesh的不同区域的属性差异来控制贴图上的属性产生不同&#xff0c;而没有贴图的话&#xff0c;一个物体只能使用一种…

Java Lambda表达式 匿名内部类 函数式接口(FunctionalInterface)

Java Lambda表达式定义背景示例匿名类实现Lambda表达式实现对比匿名类和Lambda实现Lambda表达式&#xff08;调用&#xff09;说明Lambda表达式的语法Java 1.8 新特性&#xff1a;函数式接口jdk 1.8 自带的函数式接口 &#xff08;举例&#xff09;定义 参考Oracle官网&#x…

目标检测6--R-FCN中的Position-Sensitive RoI Pooling

文章目录1.介绍2.Position-Sensitive Score Map 和 Position-Sensitive RoI Pooling3.源码参考资料欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; 1.介绍 论文: Region-based Fully Convolutional Networks 代码: R-FCN 本论文作者同9.De…

电子组装流水线MES系统实行条码质量追溯

在电子制造行业&#xff0c;保证生产过程的稳定性与对制造关键能力的改善与提升&#xff0c;是大多数制造企业的管理重心&#xff0c;而缺乏有效的方法与手段。MES系统即制造执行系统&#xff0c;是企业信息集成的纽带&#xff0c;企业实施敏捷制造战略&#xff0c;实现车间生产…

C++学习记录——십 STL初级认识、标准库string类

文章目录1、什么是STL2、STL简介3、什么是string类4、string类的常用接口说明1、常见构造函数2、容量操作3、迭代器4、其他的标准库的string类关于string类的内容&#xff0c;可以在cplusplus.com查看到。 1、什么是STL STL是C标准库的重要组成部分&#xff0c;不仅是一个可复…

指 针

1.指针指针的作用: 可以通过指针间接访问内存&#xff08;可以通过指针的保存一个地址&#xff08;指针--地址&#xff09;&#xff09;内存编号是从0开始记录的&#xff0c;一般用十六进制数字表示。可以利用指针变量保存地址指针变量的定义和使用指针变是定义语法: 数据类型 …

【MFC】模拟采集系统——图形按钮(18)

左边可以简单地使用一个组框&#xff0c;贴上背景图。当然&#xff0c;也可以使用新的对话框。 图形按钮类 1、类向导-》添加类-》选择MFC-》填入新类名称-》选择父类为 CButton 2、添加消息响应函数和虚函数&#xff1a; 消息响应mouse leave (离开&#xff09; mouse move …

CSS3 animation-fill-mode详解

CSS3 animation-fill-mode详解 定义 animation-fill-mode 属性规定当动画不播放时&#xff08;当动画完成时&#xff0c;或当动画有一个延迟未开始播放时&#xff09;&#xff0c;要应用到元素的样式。 默认情况下&#xff0c;CSS 动画在第一个关键帧播放完之前不会影响元素&…

各CCFA类核心期刊的信息汇总与评价总结(科技领域)

CCF中文期刊投稿选择之篇章二:各CCFA类核心期刊的信息汇总与评价总结上一篇章总结一部分期刊的介绍自动化学报相关信息的介绍有关录用比、审稿速度及费用的相关数据收集相关学术论坛上网友的评价与讨论期刊年度出版概况与学术热点动态&#xff08;知网&#xff09;计算机学报相…

2023年可供学习的 10 大 SaaS 知识库工具!

客户迫切希望快速找到所需的信息。在软件行业尤其如此&#xff0c;因为软件行业节奏很快&#xff0c;公司经常销售学习曲线陡峭的产品。为了减缓流失率并提高盈利能力&#xff0c;SaaS 公司正在转向知识库&#xff0c;以帮助他们让客户了解情况。什么是知识库&#xff1f;您可以…

设计模式之代理模式详解和应用

目录1 代理模式定义2 代理模式的应用场景3 代理模式的通用写法4 从静态代理到动态代理5 静态模式在业务中的应用6 动态代理在业务中的应用7 手写JDK动态代理实现原理7.1 JDK动态代理的实现原理7.2 CGLib动态代理容易踩的坑8 CGLib代理调用API及原理分析9 CGLib和JDK动态代理对比…

JVM - 高效并发

目录 Java内存模型和内存间的交互操作 Java内存模型 内存间的交互操作 内存间交互操作的规则 volatile特性 多线程中的可见性 volatile 指令重排原理和规则 指令重排 指令重排的基本规则 多线程中的有序性 线程安全处理 锁优化 锁优化之自旋锁与自适应自旋 锁优…

jvisualvm工具使用

jdk自带的工具jvisualvm&#xff0c;可以分析java内存使用情况&#xff0c;jvm相关的信息。 1、设置jvm启动参数 设置jvm参数**-Xms20m -Xmx20m -XX:PrintGCDetails** 最小和最大堆内存&#xff0c;打印gc详情 2、测试代码 TestScheduleClassGc package com.core.schedule;…

LeetCode 82. 删除排序链表中的重复元素 II

原题链接 难度&#xff1a;middle\color{orange}{middle}middle 题目描述 给定一个已排序的链表的头 headheadhead &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,…

ASML逆袭史:人、资金、技术,缺一不可

前言 近年来&#xff0c;由于众所周知的原因&#xff0c;荷兰ASML&#xff08;阿斯麦&#xff09;公司的先进半导体制造设备——光刻机&#xff0c;进入普通大众视野&#xff0c;成为人们茶余饭后谈论的焦点话题之一。 1月底&#xff0c;“美日荷三方谈判达成协议&#xff0c;可…

Selenium自动化测试Python二:WebDriver基础

欢迎阅读WebDriver基础讲义。本篇讲义将会重点介绍Selenium WebDriver的环境搭建和基本使用方法。 WebDriver环境搭建 Selenium WebDriver 又称为 Selenium2。 Selenium 1 WebDriver Selenium 2 WebDriver是主流Web应用自动化测试框架&#xff0c;具有清晰面向对象 API&…

SAP ABAP 输出结果带有空格

方法一&#xff1a; 字段内容前增加空格&#xff0c;需使用全角空格&#xff0c;使用半角空格时&#xff0c;ALV显示无效&#xff0c;空格无法显示&#xff0c; 全角与半角的切换方法&#xff1a;shift空格切换&#xff0c; 如下的标记部分&#xff0c;要想通过ALV显示空格&…

mfc140u.dll丢失的解决方法,mfc140u.dll文件修复

mfc140u.dll丢失的解决方法&#xff0c;其实要解决这个问题一点都不难&#xff0c;我们主要知道是什么原因造成的&#xff0c;那么就可以轻松的解决。 一.mfc140u.dll是什么 "MFC140u.dll"是一个Windows动态链接库文件&#xff0c;它是Microsoft Visual C 2015运行…