有序表的应用:设计一个增、删、查数据的时间复杂度均为O(logN)的结构

news2024/12/23 14:23:57

1、题目描述

设计一个结构包含如下三个方法:

void add(int index, int num); //把num加入到index位置
int get(int index); //取出index位置的值(是自然序的index位置,非排序后)
void remove(int index); //把index位置上的值删除

要求三个方法时间复杂度 O ( l o g N ) O(logN) O(logN)

2、思路分析

ArrayList 中删除一个元素需要将其后的元素全部往前移动一位,时间复杂度为 O ( N ) O(N) O(N)LinkedList 中虽然删除一个元素的时间复杂度很低 O ( 1 ) O(1) O(1),但是要找到这个待删除的元素得从头开始遍历,所以整体时间复杂度仍然为 O ( N ) O(N) O(N)

脚本语言中使用的“数组”好像什么功能都能完成,且很高效,是因为该数组底层并不是单纯的数组或双链表,只是高度改进后起名为“数组”。

题目补充说明:add(int index, int num) 方法在 index 位置加入 num,意思是假设原数组是 [3, 5, 2, 4],如果在 1 位置加入 7,则数组变成 [3, 7, 5, 2, 4]。

使用有序表可以设计出题目要求的复杂度的三个方法的结构。但是要注意:

  1. 为了区分值相同的两个数,在外面再封装一层。也就是说如果有多个相同的值,在树上就会有多个值相同的节点,通过内存地址区分开。每个节点记录的size 就是平衡因子,即以该节点为根的树上的节点个数。

  2. 改进的有序表并不是按照 key 进行排序的,而是按照自然时序。即当前节点的左树的自然时序都早于当前节点,右树的自然时序都晚于当前节点。

  3. 如果能维持一个以自然时序排列的树,无论左旋还是右旋,自然时序都维持正确,即不会改变这些数的相对次序。

  4. 对于一棵已经生成的树,加入新的数后无论如何旋转都不会改变它的相对次序,那么新加入的数应该挂在树的哪个位置上呢?

以自然时序排列组织的树 以及 旋转后相对次序没有发生改变 举例:

输入的数依次为[5, 3, 5]
在这里插入图片描述

再举例addremove 操作:

在这里插入图片描述
删除4位置的数后的树对应的自然时序就是[7, 7, 3, 5, 6]。

3、代码实现

import java.util.ArrayList;

//本质就是不去重版本的有序表/Size Balanced Tree
public class AddRemoveGetIndexGreat {
	
	//没有key,因为参与排序的并不是key,而是隐含的自然时序
	public static class SBTNode<V> {
		public V value;
		public SBTNode<V> l;
		public SBTNode<V> r;
		public int size; //平衡因子,也参与业务

		public SBTNode(V v) {
			value = v;
			size = 1;
		}
	}

	public static class SbtList<V> {
		private SBTNode<V> root;

		private SBTNode<V> rightRotate(SBTNode<V> cur) {
			SBTNode<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<V> leftRotate(SBTNode<V> cur) {
			SBTNode<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;
		}

		private SBTNode<V> maintain(SBTNode<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) {
				cur = rightRotate(cur);
				cur.r = maintain(cur.r);
				cur = maintain(cur);
			} else if (leftRightSize > rightSize) {
				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;
		}

		//root这棵树上的index位置添加节点cur,这个cur一定不是重复的,因为封装了一层,有不同的内存地址
		private SBTNode<V> add(SBTNode<V> root, int index, SBTNode<V> cur) {
			if (root == null) {
				return cur;
			}
			//以root为根的树上的节点个数加1,可以理解为与之前“区间和个数”问题中的all数据项合并了
			root.size++; 
			//左树及根节点一共有多少个节点
			int leftAndHeadSize = (root.l != null ? root.l.size : 0) + 1; 
			if (index < leftAndHeadSize) {
				root.l = add(root.l, index, cur);
			} else {
				root.r = add(root.r, index - leftAndHeadSize, cur); //在右树上位于自然时序的第几位
			}
			root = maintain(root);
			return root;
		}

		private SBTNode<V> remove(SBTNode<V> root, int index) {
			//找到要删除的节点过程中的沿途节点的size都要减1
			root.size--;
			int rootIndex = root.l != null ? root.l.size : 0;
			if (index != rootIndex) {
				if (index < rootIndex) {
					root.l = remove(root.l, index);
				} else {
					root.r = remove(root.r, index - rootIndex - 1);
				}
				return root;
			}
			if (root.l == null && root.r == null) {
				return null;
			}
			if (root.l == null) {
				return root.r;
			}
			if (root.r == null) {
				return root.l;
			}
			SBTNode<V> pre = null;
			SBTNode<V> suc = root.r;
			suc.size--;
			while (suc.l != null) {
				pre = suc;
				suc = suc.l;
				suc.size--;
			}
			if (pre != null) {
				pre.l = suc.r;
				suc.r = root.r;
			}
			suc.l = root.l;
			suc.size = suc.l.size + (suc.r == null ? 0 : suc.r.size) + 1;
			return suc;
		}

		private SBTNode<V> get(SBTNode<V> root, int index) {
			int leftSize = root.l != null ? root.l.size : 0;
			if (index < leftSize) {
				return get(root.l, index);
			} else if (index == leftSize) {
				return root;
			} else {
				return get(root.r, index - leftSize - 1);
			}
		}
		
		//add方法:在index位置加入num
		public void add(int index, V num) {
			SBTNode<V> cur = new SBTNode<V>(num); //先封装一层,以区分相同的num
			if (root == null) {
				root = cur;
			} else {
				if (index <= root.size) {
					root = add(root, index, cur);
				}
			}
		}

		public V get(int index) {
			SBTNode<V> ans = get(root, index);
			return ans.value;
		}

		public void remove(int index) {
			if (index >= 0 && size() > index) {
				root = remove(root, index);
			}
		}

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

	}

	// 通过以下这个测试,
	// 可以很明显的看到LinkedList的插入、删除、get效率不如SbtList
	// LinkedList需要找到index所在的位置之后才能插入或者读取,时间复杂度O(N)
	// SbtList是平衡搜索二叉树,所以插入或者读取时间复杂度都是O(logN)
	public static void main(String[] args) {
		// 功能测试
		int test = 50000;
		int max = 1000000;
		boolean pass = true;
		ArrayList<Integer> list = new ArrayList<>();
		SbtList<Integer> sbtList = new SbtList<>();
		for (int i = 0; i < test; i++) {
			if (list.size() != sbtList.size()) {
				pass = false;
				break;
			}
			if (list.size() > 1 && Math.random() < 0.5) {
				int removeIndex = (int) (Math.random() * list.size());
				list.remove(removeIndex);
				sbtList.remove(removeIndex);
			} else {
				int randomIndex = (int) (Math.random() * (list.size() + 1));
				int randomValue = (int) (Math.random() * (max + 1));
				list.add(randomIndex, randomValue);
				sbtList.add(randomIndex, randomValue);
			}
		}
		for (int i = 0; i < list.size(); i++) {
			if (!list.get(i).equals(sbtList.get(i))) {
				pass = false;
				break;
			}
		}
		System.out.println("功能测试是否通过 : " + pass);

		// 性能测试
		test = 500000;
		list = new ArrayList<>();
		sbtList = new SbtList<>();
		long start = 0;
		long end = 0;

		start = System.currentTimeMillis();
		for (int i = 0; i < test; i++) {
			int randomIndex = (int) (Math.random() * (list.size() + 1));
			int randomValue = (int) (Math.random() * (max + 1));
			list.add(randomIndex, randomValue);
		}
		end = System.currentTimeMillis();
		System.out.println("ArrayList插入总时长(毫秒) : " + (end - start));

		start = System.currentTimeMillis();
		for (int i = 0; i < test; i++) {
			int randomIndex = (int) (Math.random() * (i + 1));
			list.get(randomIndex);
		}
		end = System.currentTimeMillis();
		System.out.println("ArrayList读取总时长(毫秒) : " + (end - start));

		start = System.currentTimeMillis();
		for (int i = 0; i < test; i++) {
			int randomIndex = (int) (Math.random() * list.size());
			list.remove(randomIndex);
		}
		end = System.currentTimeMillis();
		System.out.println("ArrayList删除总时长(毫秒) : " + (end - start));

		start = System.currentTimeMillis();
		for (int i = 0; i < test; i++) {
			int randomIndex = (int) (Math.random() * (sbtList.size() + 1));
			int randomValue = (int) (Math.random() * (max + 1));
			sbtList.add(randomIndex, randomValue);
		}
		end = System.currentTimeMillis();
		System.out.println("SbtList插入总时长(毫秒) : " + (end - start));

		start = System.currentTimeMillis();
		for (int i = 0; i < test; i++) {
			int randomIndex = (int) (Math.random() * (i + 1));
			sbtList.get(randomIndex);
		}
		end = System.currentTimeMillis();
		System.out.println("SbtList读取总时长(毫秒) :  " + (end - start));

		start = System.currentTimeMillis();
		for (int i = 0; i < test; i++) {
			int randomIndex = (int) (Math.random() * sbtList.size());
			sbtList.remove(randomIndex);
		}
		end = System.currentTimeMillis();
		System.out.println("SbtList删除总时长(毫秒) :  " + (end - start));

	}
}

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

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

相关文章

【Linux】vim拒绝服务安全漏洞修复

根据国家信息安全漏洞共享平台于2023年2月19日发布的安全漏洞通知&#xff0c;Linux系统自带的vim编辑器存在两个高危安全漏洞&#xff08;CNVD-2023-09166、CNVD-2023-09647&#xff09;&#xff0c;攻击者可以利用该漏洞发起拒绝服务攻击&#xff0c;并可能运行&#xff08;恶…

【编程入门】应用市场(Vue版)

背景 前面已输出多个系列&#xff1a; 《十余种编程语言做个计算器》 《十余种编程语言写2048小游戏》 《17种编程语言10种排序算法》 《十余种编程语言写博客系统》 《十余种编程语言写云笔记》 《N种编程语言做个记事本》 目标 为编程初学者打造入门学习项目&#xff0c;使…

双因素方差分析

一、案例与数据 一家大型商业银行在多地区设有分行&#xff0c;其业务主要是进行基础设施建设&#xff0c;国家重点项目建设&#xff0c;固定资产投资等项目的贷款。近年来&#xff0c;该银行的贷款额平稳增长&#xff0c;但不良贷款额也有较大比例的提高&#xff0c;这给银行…

数据库

一、数据库系统管理 ACID&#xff0c;是指数据库管理系统&#xff08;DBMS&#xff09;在写入或更新资料的过程中&#xff0c;为保证事务&#xff08;transaction&#xff09;是正确可靠的&#xff0c;所必须具备的四个特性&#xff1a;原子性&#xff08;atomicity&#xff0…

【微信小程序】一文带你吃透开发中的常用组件

写在前面 小程序中的组件也是由宿主环境提供的&#xff0c;开发者可以基于组件快速搭建出漂亮的页面结构。 官方把小程序的组件分为了9大类&#xff0c;分别是: 1.视图容器 2.基础内容 3.表单组件 4.导航组件 5.媒体组件 6.地图组件 7.画布组件 …

QMap 判断是否value是否已经存在,结合Sleep函数测试

网上查了资料&#xff0c;基本说的都是通过.value判断是否已经之前的key值&#xff0c;但是尝试.了一下发现有.key的函数&#xff0c;对比着来就感觉这个函数是用来判断是否已经存在value值&#xff0c;于是开始百度也几乎没有找到相关资料&#xff0c;只好自己看官方文档&…

Fortinet推出新一代自研安全芯片,跨所有网络边缘加速网络与安全融合

专注网络与安全融合的全球网络安全领导者 Fortinet&#xff08;NASDAQ&#xff1a;FTNT&#xff09;&#xff0c;近日宣布推出新一代自研安全芯片 FortiSP5&#xff0c;作为 Fortinet ASIC 技术的最新突破&#xff0c;有力推动了分布式网络边缘安全的重大飞跃。FortiSP5 源自 F…

【LeetCode】剑指 Offer 10- I. 斐波那契数列 p74 -- Java Version

题目链接&#xff1a; 1. 题目介绍&#xff08;&#xff09; 写一个函数&#xff0c;输入 n &#xff0c;求斐波那契&#xff08;Fibonacci&#xff09;数列的第 n 项&#xff08;即 F(N)&#xff09;。斐波那契数列的定义如下&#xff1a; F(0) 0, F(1) 1F(N) F(N - 1) F…

修复 K8s SSL/TLS 漏洞(CVE-2016-2183)指南

作者&#xff1a;老 Z&#xff0c;中电信数智科技有限公司山东分公司运维架构师&#xff0c;云原生爱好者&#xff0c;目前专注于云原生运维&#xff0c;云原生领域技术栈涉及 Kubernetes、KubeSphere、DevOps、OpenStack、Ansible 等。 前言 测试服务器配置 主机名IPCPU内存系…

模电中的负反馈

文章目录一、反馈是什么&#xff1f;二、负反馈对于放大性能的影响1.负反馈的作用三、正反馈总结– 一、反馈是什么&#xff1f; 反馈的定义&#xff1a;凡是将放大电路输出端信号&#xff08;电压或电流&#xff09;的一部分或者全部引回到输入端&#xff0c;与输入信号叠加…

【C语言】宏

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a;> c语言学习 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是…

打造Ai作图studio需要哪些工具

这篇文章依然是比较轻松的方式跟大家介绍Ai生成会使用到的一些工具&#xff0c;希望这些工具可以帮助到你更好的更稳定的快捷的生成高质量图片。说来轻松其实也不算轻松&#xff0c;虽然我已经按照生成的链路对工具做了规整。但是里面涉及到的工具其实确实不算少&#xff0c;并…

2023-02-20 Qt 5.13.1 + OpenCV 4.5.4环境编译

引言 OpenCV图像处理在Qt中编译记录。 之前一直是在Python中使用OpenCV&#xff0c;Python中使用某些模块使用pip工具很容易将对应的模块安装在系统中。根据项目需求项目都要转移在国产化中使用&#xff0c;为了适应国产化需求&#xff0c;将代码转移到Qt开发环境中&#xff0c…

django项目实战四(django+bootstrap实现增删改查)进阶时间控件

接上一篇《django项目实战三&#xff08;djangobootstrap实现增删改查&#xff09;进阶分页》 知识点&#xff1a; 使用bootstrap-datepicker实现时间控件 一、优化layout.html模版 主要新增2个块 {% block css %}{% endblock %}{% block js %}{% endblock %} {% load static…

nginx.conf配置方法详细介绍

从前面的内容学习中&#xff0c;我们知道Nginx的核心配置文件默认是放在/usr/local/nginx/conf/nginx.conf&#xff0c;这一节&#xff0c;我们就来学习下nginx.conf的内容和基本配置方法。读取Nginx自带的Nginx配置文件&#xff0c;我们将其中的注释部分【学习一个技术点就是在…

第20讲:Python列表、元组、字符串使用自定义排序规则

文章目录1.自定义排序方法2.常用作自定义排序的函数、方法3.列表、元组、字符串自定义排序方法3.1.当列表、元组中元素为字符串的排序规则3.2.三者采用str.lower方法实现自定义排序3.2.三者采用len函数实现自定义排序1.自定义排序方法 列表、元组、字符串都可以进行排序&#…

友元的学习

&#x1f601;友元的简介类的主要特点之一是数据隐藏&#xff0c;即类的私有成员无法在类的外部作用域之外访问&#xff0c;但是&#xff0c;有时候需要在类的外部访问类的私有成员&#xff0c;这个时候就需要使用友元函数。友元函数是一种特权函数&#xff0c;c允许这哥特权函…

分享在线预约系统制作步骤_在线预约链接怎么做

在微信小程序上进行在线预约&#xff0c;不管是商家还是顾客&#xff0c;都可以自由选择时间&#xff0c;顾客还可以通过预约小程序&#xff0c;了解到所选服务的详情和功能特色&#xff0c;不必等到去店内听介绍&#xff0c;顾客能节省等候时间&#xff0c;商家能解放招待人力…

解决:Vmware Workstation 和 Vmware ESXI 创建虚拟机Ubuntu20.04时界面显示不全,无法点击Continue进行下一步

目录 Vmware Workstation Vmware ESXI Vmware Workstation 1.如下图&#xff0c;到了这一步可以按 CTRL ALT T 调出命令终端 2. 终端输入 xrandr --size 1280x800 此命令是调整屏幕大小 3.此时已经显示屏幕完整信息 Vmware ESXI 安装workstation时那种调整界面大小的方…

每日学术速递2.21

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.T2I-Adapter: Learning Adapters to Dig out More Controllable Ability for Text-to-Image Diffusion Models 标题&#xff1a;T2I-Adapter&#xff1a;学习Adapter&#xff0c;为…