当前位置: 首页 > news >正文

Unity 3D常用的数据结构

目录

    • 数组
        • 使用场景
      • ArrayList数组
        • ArrayList的缺点
      • List\<T\>数组
        • List\<T\>有以下3点好处
    • 链表
          • 链表与数组的不同之处
          • 链表的优势
          • 数组和链表的应用场景
      • LinkedList\<T\>
        • C#中内置的双向链表LinkedList
        • 使用场景
      • 队列(Queue\<T\>)和栈(Stack\<T\>)
        • queue队列
          • 内部实现
          • 内部实现
      • Hashtable哈希表
        • 如何处理哈希冲突
          • 避免哈希冲突
          • 解决哈希冲突
            • 开放寻址法的简单实现——线性探查(Linear Probing)
            • 针对线性探查方式所存在的问题,一种改进的方式为二次探查(Quadratic Probing)
        • 二度哈希
          • 二度哈希的工作原理
          • loadFactor
          • Hashtable类的实例中添加新元素时,需要检查以保证元素与空间大小的比例不会超过最大比例。如果超过了,Hashtable类实例的空间将被扩充。空间扩充的步骤如下
      • Dictionary\<K,T\>字典
        • 冲突解决机制
        • Dictionary<K,T>类的缺点
        • 使用场景

数组

使用场景

元素的数量是固定的,并且需要使用下标时。

ArrayList数组

为了解决Array创建时必须指定长度,以及只能存放相同类型的缺点而推出的数据结构。

ArrayList的缺点
  • ArrayList是类型不安全的。因为把不同的类型都当作Object来做处理,很有可能会在使用ArrayList时发生类型不匹配的情况。
  • 数组存储值类型时并未发生装箱,但是ArrayList由于把所有类型都当作了Object,所以不可避免的是当插入值类型时会发生装箱操作,在索引取值时会发生拆箱操作。因此在频繁读写 ArrayList 时会产生额外的开销,导致性能下降。

List<T>数组

可以认为List<T> 类是 ArrayList 类的泛型等效类。

List<T>有以下3点好处
  1. 即确保了类型安全。因此List<T>是类型安全的。
  2. 取消了装箱和拆箱的操作,以及由于引入泛型而无需运行时类型检查。因此List<T>是高性能的。
  3. 融合了Array可以快速访问的优点,以及ArrayList长度可以灵活变化的优点。

链表

链表与数组的不同之处

数组中的内容在内存中是连续排列的,可以通过下标来访问。
链表中内容的顺序则是由各个对象的指针所决定的,这就决定了其内容的排列不一定是连续的,所以不能通过下标来访问。

链表的优势

使用链表最主要的优势就在于向链表中插入或删除节点时,无需考虑调整结构的容量。相反的对于数组来说容量始终是固定的,且数组中的内容在内存中是连续的。因此如果需要存放更多的数据,则面临着需要调整数组容量的现实,这就会引发新建数组、数据拷贝等一系列复杂且影响效率的操作。即使是List<T>类,虽然其对开发人员隐藏了容量调整的复杂性,但实质上性能的损耗是必须考虑的。

数组和链表的应用场景

数组适合数据的数量是有上限,且需要快速访问其元素内容的情况.
链表适合元素数量不固定且需要经常增删结点的情况。

LinkedList<T>

C#中内置的双向链表LinkedList

在Unity 3D开发过程中,由于C#已经为开发者封装了一个对应链表的类——LinkedList<T>类。因此可以很方便地通过LinkedList<T>来实现链表的功能。而和LinkedList<T>类相配套的,C#还提供了链表的结点类——LinkedListNode<T>类以用来代表链表中的结点,LinkedList<T>对象中的每个节点都属于LinkedListNode<T>类型。由于LinkedList<T>是双向链表,因此每个节点向前指向Next节点向后指向Previous节点
需要说明的一点是,LinkedList<T>类的插入和移除的运算复杂度都是O(1)。而由于该列表还维护内部计数,因此获取Count属性的运算复杂度也为 O(1)。

如何创建一个链表LinkedList<T>,以及最常见的几种操作。

  • AddFirst,将一个新结点加入该链表的第一个结点的位置;
  • RemoveFirst,将第一个结点移除;
  • AddLast,将一个新节点加入该链表最后一个结点的位置;
  • 以及在某个结点前后插入新的结点的AddBefore和AddAfter方法。
  • 对链表中的结点类LinkedListNode的各种操作。
使用场景

元素需要能够在列表的两端添加时。否则使用List<T>。

队列(Queue<T>)和栈(Stack<T>)

queue队列
内部实现

在Queue<T>内部,有一个存放类型为T的对象的环形数组,并通过head 和tail变量来指向该数组的头和尾。当使用Enqueue方法将新的元素入列时,会判断队列的长度是否足够。若不足,则依据增长因子来增加容量,例如当为初始的2.0时,则队列容量增长2倍。
在默认情况下,Queue<T>的初始化容量是32,但是也可以通过构造函数指定容量
元素的进出顺序是先进先出(FIFO)

栈(Stack)又名堆栈,它和队列一样是一种运算受限的线性表
其限制是仅允许在表的一端进行插入和删除的操作运算。这一端称为栈顶,相对的,把另一端称为栈底。
向一个栈插入新元素称为进栈、入栈或压栈。
一个栈删除元素称为出栈或退栈,它是把栈顶元素删除,使其相邻的元素成为新的栈顶元素。
元素的进出顺序是后进先出(LIFO)

内部实现

内部同样使用了数组来实现。内部结构可以通过一个垂直的数组来形象的表示。

Hashtable哈希表

哈希表(Hash Table,也叫散列表),是根据关键码/值(Key/value)而直接进行访问的数据结构。也就是说,它通过把关键码/值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫哈希函数或散列函数,存放记录的数组就叫哈希表。

如何处理哈希冲突

处理哈希冲突时,也有两种思路——避免解决

  • 冲突避免机制(Collision Avoidance)
  • 冲突解决机制(Collision Resolution)
避免哈希冲突

避免哈希冲突的一个方法就是尽可能先择合适的哈希函数。

解决哈希冲突
  1. 将要插入的元素放到另一块空间中,因为相同的哈希位置已经被占用了。
  2. 开放寻址法(Open Addressing)
开放寻址法的简单实现——线性探查(Linear Probing)
  1. 当插入新的元素时,使用哈希函数在哈希表中定位元素位置。
  2. 检查哈希表中该位置是否已经存在元素。如果该位置内容为空,则插入并返回,否则进行步骤3的操作。
  3. 如果该位置为i,则检查i+1是否为空。如果已被占用,则检查i+2。依此类推,直到找到一个内容为空的位置。

线性探查(Linear Probing)方式虽然简单,但并不是解决冲突的最好策略,因为它会导致同类哈希的聚集(Primary Clustering)。这会导致搜索哈希表时,冲突依然存在。

针对线性探查方式所存在的问题,一种改进的方式为二次探查(Quadratic Probing)

即每次检查位置空间的步长为平方倍数。也就是说,如果位置s被占用,则首先检查s+12处,然后检查s-12、s+22、s-22、s+32…以此类推,而不是像线性探查那样以s+1、s+2…方式增长。

尽管如此,二次探查同样也会导致同类哈希聚集问题(Secondary Clustering)。

二度哈希

当在哈希表中添加或获取一个元素时,会发生哈希冲突。前面简单地介绍了两种冲突解决策略,即线性探查(Linear Probing)和二次探查(Quadratic Probing)。
二度哈希使用了Θ(m2)种探查序列,而线性探查(Linear Probing)和二次探查(QuadraticProbing)使用了Θ(m)种探查序列,因此二度哈希提供了更好的避免冲突的策略。

二度哈希的工作原理

有一个包含一组哈希函数H1…Hn的集合。当需要从哈希表中添加或获取元素时,首先使用哈希函数H1。如果导致冲突,则尝试使用H2。以此类推,直到Hn。所有的哈希函数都与H1十分相似,不同的是它们选用的乘法因子(multiplicative factor)。
当使用二度哈希时,重要的是在执行了hashsize次探查后,哈希表中的每一个位置都有且只有一次被访问到。也就是说,对于给定的key,对哈希表中的同一位置不会同时使用H1和H2 。在Hashtable类中使用二度哈希公式,其始终保持(1 +((GetHash(key) >> 5) + 1) %(hashsize - 1)hashsize互为素数 (两数互为素数表示两者没有共同的质因子)

loadFactor

Hashtable类中还包含了一个私有成员变量loadFactor,loadFactor指定了哈希表中元素数量与位置(slot)数量之间的最大比例。 例如,如果loadFactor 等于0.5,则说明哈希表中只有一半的空间存放了元素值,其余一半都为空。
哈希表的构造函数允许用户指定loadFactor值,定义范围为0.1至1.0。然而不管提供的值是多少,范围都不会超过72%。即使传递的值为1.0,Hashtable类的loadFactor值还是0.72。微软官方认为loadFactor的最佳值为0.72,这平衡了速度与空间。因此,虽然默认的loadFactor为1.0,但系统内部却自动地将其改变为0.72。所以,建议使用缺省值1.0(但实际上是 0.72)。

Hashtable类的实例中添加新元素时,需要检查以保证元素与空间大小的比例不会超过最大比例。如果超过了,Hashtable类实例的空间将被扩充。空间扩充的步骤如下
  1. Hashtable类实例的位置空间几乎被翻倍。准确地说,位置空间值从当前的素数值增加到下一个最大的素数值。
  2. 因为二度哈希时,Hashtable类实例中的所有元素值将依赖于Hashtable类实例的位置空间值,所以Hashtable类实例中保存的所有值也需要重新二度哈希。

Dictionary<K,T>字典

Dictionary<K,T>使用强类型来限制Key和Item,当创建Dictionary<K,T>实例时,必须指定Key和Item的类型。

冲突解决机制

Dictionary<K,T>还采用了不同的冲突解决策略(Collision Resolution Strategy),这种技术称为链接技术(Chaining)。
链接技术(Chaining)将采用额外的数据结构来处理冲突。Dictionary<K,T>中的每个位置(slot)都映射到了一个链表。当冲突发生时,冲突的元素将被添加到桶(bucket)列表中。

Dictionary<K,T>类的缺点

它的缺点就是空间。以空间换时间,通过更多的内存开销来满足对速度的追求。在创建字典时,可以传入一个容量值,但实际使用的容量并非该值。而是使用不小于该值的最小质数作为它使用的实际容量,容量的最小值是 3。当有了实际容量后,并非直接实现索引,而是通过创建额外的两个数组来实现间接索引,即int[] buckets和Entry[] entries两个数组。因此面临的情况就是,即便新建了一个空的字典,那么伴随而来的是两个长度为3的数组。所以当处理的数据不多时,还是慎重使用字典为好,在很多情况下使用数组也是可以的

使用场景

需要使用键值对(KeyValue)来快速添加和查找,并且元素没有特定的顺序时。

相关文章:

  • C++特性之一:继承
  • C++中PostMessage和SendMessage函数的用途、区别、使用方法及使用示例
  • IDEA启动项目到一半后卡住但不报错的解决方法分享
  • [MYSQL数据库]- 索引
  • ElasticSearch深度分页问题如何解决
  • 数据结构:图的存储与遍历(待续)
  • 同态滤波算法详解
  • Docker进阶:深入了解 Dockerfile
  • 采购代购系统独立站,接口采集商品上货
  • L1-039 古风排版(C++)
  • 基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的条形码二维码检测系统(深度学习+UI界面+训练数据集+Python代码)
  • Oracle 死锁、指标汇总
  • 有点NB的免费wordpress主题模板
  • Neo4j 批量导入数据 从官方文档学习LOAD CSV 命令 小白可食用版
  • PHP+Lunix+GIT 如何快速使用宝塔WebHook快速自动化部署
  • 2017-09-12 前端日报
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • Android 控件背景颜色处理
  • Angularjs之国际化
  • ComponentOne 2017 V2版本正式发布
  • github指令
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • orm2 中文文档 3.1 模型属性
  • python_bomb----数据类型总结
  • Python学习之路16-使用API
  • spark本地环境的搭建到运行第一个spark程序
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 利用DataURL技术在网页上显示图片
  • 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
  • 入门到放弃node系列之Hello Word篇
  • Android开发者必备:推荐一款助力开发的开源APP
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • #13 yum、编译安装与sed命令的使用
  • #ifdef 的技巧用法
  • $$$$GB2312-80区位编码表$$$$
  • $.ajax()
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (2.2w字)前端单元测试之Jest详解篇
  • (3)(3.2) MAVLink2数据包签名(安全)
  • (C语言)求出1,2,5三个数不同个数组合为100的组合个数
  • (C语言)输入自定义个数的整数,打印出最大值和最小值
  • (笔记)Kotlin——Android封装ViewBinding之二 优化
  • (附源码)计算机毕业设计SSM疫情居家隔离服务系统
  • (论文阅读31/100)Stacked hourglass networks for human pose estimation
  • (十) 初识 Docker file
  • (一)基于IDEA的JAVA基础12
  • (原創) 是否该学PetShop将Model和BLL分开? (.NET) (N-Tier) (PetShop) (OO)
  • *Django中的Ajax 纯js的书写样式1
  • .NET Framework 3.5中序列化成JSON数据及JSON数据的反序列化,以及jQuery的调用JSON
  • .NET 简介:跨平台、开源、高性能的开发平台
  • .NET开源全面方便的第三方登录组件集合 - MrHuo.OAuth
  • .php结尾的域名,【php】php正则截取url中域名后的内容
  • @Conditional注解详解