[完结]以C++与Java为例,详解数据结构的动态增长策略

前言

本文改编自小夕的订阅号文章《【萌味】小夕说,不了解动态空间增长的程序喵都是假喵(上)》、《【萌味】小夕说,不了解动态空间增长的程序喵都是假喵(中)》、《【萌味】小夕说,不了解动态空间增长的程序喵都是假喵(下)》。萌气已过滤,需要呼吸萌气的读者请戳上面原文哦。

笔者学了数据结构后,知道了链表、树、哈希表等数据结构与静态数组的固定容量不同,它们是可以动态添加元素的。这种数据结构的初始大小可能很小,甚至几乎为零,但是随着新元素的加入,其大小(内存空间占用)会不断增长,这个过程就叫做动态空间增长。

那么问题来了,所有支持动态空间增长的数据结构都是相同的增长方式吗?了解这个又有什么意义呢?

笔者曾经很傻很天真的认为所有支持动态空间增长的数据结构都是每增加一个元素,数据结构的大小就增加1个单位。直到在一个中规模机器学习任务的数据预处理过程中遇到了“内存爆炸”的问题,即笔者明明计算的内存够用,但是笔者可怜的电脑的内存却意外爆满了。这是怎么回事呢?

笔者为了避免讲解太过抽象,所以建议:如果您擅长C++,那么请注意一下Vector数据结构;如果擅长Java,请注意一下ArrayList、LinkedList、哈希系列(HashSet/ HashTable/ HashMap);如果您不用Java也不用C++,或者已经脱离XX编程语言的层次,那么请注意一下可变数组(可增长顺序表)、链表、哈希(散列)。笔者将基于上述数据结构展开讲解。

递增式扩容

对于Java的LinkedList,也就是数据结构中的链表,其空间增长方式就是笔者一开始的设想:每增加一个元素,其大小就增加一个单位(这里的一个单位就是指一个元素占用的空间大小)。原因就在于链表在内存中的存储可以是不连续的。例如一个依次由节点1、节点2、节点3连接而成的链表在计算机内存中完全有可能是下面的存储方式。


v2-d4f1c4363130b3751c0b86b481e2baa1_b.png

这样的话,链表每增加一个元素,只需要在内存中找个缝将新元素插进去就可以。所以如果笔者手里有n个元素想插入链表,则需要开辟n次内存,每次均开辟一个元素的大小。

这种数据结构建立后,每次数据结构要扩容时均增加固定空间大小的做法被称为【递增式扩容】。显然链表的空间增长方式就是递增式扩容,而且递增的单位为1(这里是指1个单位,即一个结点的大小)。


可以看到,如果是链表数据结构,或者是底层基于链表而实现的数据结构,采用递增式扩容是最优选择。因为要增加一个元素,则最好的情况就是其他什么都不动,仅仅是为该元素开辟一个单位的空间,然后塞入该元素。而递增式扩容用于链表确实达到了这个最理想情况呢。因此,对于链表,以及底层基于链表结构实现的数据结构,都是采用递增式扩容即可达到最优扩容效率(最优动态空间增长)。例如哈希中的横向增长,再如基于链表实现的树(如Java中的TreeSet,TreeMap等)。

因此在操纵大量数据的时候,尤其机器学习任务中常见的操纵大量样本的时候,在内存的问题上可以安心的使用此类数据结构,不会导致“内存爆炸”的问题,内存只会慢慢的起火然后轻轻的告诉你满了。当然了,不能仅考虑内存,有时操纵大量数据时对数据处理效率要求更高,这时候就要舍内存保速度啦。

那么哪些常见数据结构采用递增式扩容无法达到最优呢?它们有什么共性吗?还有,小夕遇到的内存爆炸是怎么回事呢?

源于数组

那么对于C++中的Vector,Java中的ArrayList、HashSet/ HashTable/ HashMap,也就是数据结构中的可变数组、哈希来说,空间增长方式是怎样呢?可能有读者此时在想“这些数据结构又不一样,怎么放到一起讨论了呢?”

其实这些表面看似不同的东西,底层的实现方式确是一样的,它们在底层都是通过操纵静态数组来实现他们的动态空间增长功能,下文会详细介绍。

讲到这里,可能有读者会记得笔者在上一篇中也提到过哈希,说哈希的横向增长是基于链表的,因此递增式扩容是最优动态空间增长方案。那这一篇中又说哈希是基于静态数组的,这是怎么回事呢?下面给没有接触过哈希的读者先科普一下哈希:

哈希的横向增长是基于链表实现的,即当新元素的哈希值与已有元素哈希值相同时,新元素会插入到某个链表中,因此是递增式增长。但是更多的情况下,哈希是纵向增长的。学过数据结构的宝宝知道,哈希在纵向上就是一个指针数组,数组的每个索引值即代表一个哈希值,数组的每个元素是一个指向某链表的指针。画个图来看就是这样的。

v2-b95993321c6f9de9d518a99d3bf6a2b3_b.png

所以,在本篇文章中,我们不看哈希的横向增长,只关注纵向增长,此时显然是基于静态数组实现的。

基于数组的扩容原理

下面笔者直接以“数据结构”代称所有这些基于静态数组实现的动态空间分配的数据结构,包括但不限于上文提到的C++中的Vector(即数据结构中的动态数组),Java中的ArrayList(即动态数组)、Hash系列(即哈希/散列)等。

具体来说,如何用静态数组实现上述的动态空间增长的数据结构呢?其实很简单,每次数据结构要扩容时只需要依次进行下述操作就可以完成:

  1. 开辟一段新的内存空间,空间大小就是扩容后的数据结构大小。

  2. 把旧数据结构,也就是旧的内存空间的元素一个个的复制到新的内存空间

  3. 释放旧的内存空间(代码上就是删除旧空间的指针,当然像Java这种自动管理内存的语言就不用操心这一步了)

通过上述扩容的三步操作,可以看到每次哈希表的扩容操作的代价还是挺大的。第1步和第3步的代价不算大,但是第2步的代价会随着要搬移元素数量的增加而直线上升。所以这就相当于一个完整搬家的过程:先买个新房子,再把旧房子里的全部家当搬到新房子里去,再把旧房子注销。

加倍式扩容

既然代价如此之大,那么显然我们要尽量减小扩容次数。怎么扩呢?一个很creative的想法就是每次使数据结构变为自身的两倍!再机智一点,每次使数据结构变为自身的N倍!其中N只要大于1就可以!口说无凭,下面给出算法分析的过程。

假如数据结构A使用【递增式扩容】。每次数据结构满了的时候就固定的增加10个单位的空间(增加单位的数量不会影响最终分析出来的复杂度哦)。好,那小夕现在手里有n个元素想添加进数据结构,假如n的数值很大,远远的大于10,那么要执行多少次扩容操作呢?当然是n/10次啦~这n/10次扩容的累计开销大约为

cost=10+2*10+3*10+...+(n/10)*10

计算一下这个级数,就是

cost=[(n/10)/2]*[(n/10)+1]*10

所以复杂度是O(n^2)的数量级,所以平均每个元素被添加进哈希表时的开销为cost/n,也就是O(n)的复杂度。

假如数据结构B使用【加倍式扩容】。每次数据结构满了的时候,数据结构的大小就变成原来的2倍(与之前同样的,这个倍数取不同的值并不会影响最终分析出来的复杂度,当然倍数必须大于1!)。同样,将n个元素添加进数据结构,假如n的数值很大,远远的大于2,那么要执行的扩容操作的次数是log2n!令c=log2n,则这c次扩容操作的累计开销为cost=2^1+2^2+...+2^c

这个级数的和为

cost=[2/(1-2)]*(1-2c)

代入c=log2n得cost=2(n-1)。也就是说复杂度为O(n),所以平均每个元素被添加进哈希表时的开销为cost/n,也就是O(1)的复杂度!注意前面我们计算过,这里数据结构A(递增式扩容)的复杂度为O(n)!

讲到这里读者应该清楚了吧?所以如果有一天你要自己写一个基于数组的动态空间增长的数据结构的话,可千万不要写成递增式扩容了。

内存爆炸

正是因为这类数据结构采用了加倍式扩容,导致这类数据结构申请内存的时候翻倍翻倍的要。结果当时在那个中规模机器学习任务中,笔者算的是一个超大哈希表只需要占用5个G作右的内存空间,而实际上在往这个哈希表加数据时,从4个G直接爆到了接近8个G,导致笔者内存8G的小电脑直接崩盘了。

等等,看似此文可以结了,实际上,敏锐的读者可能想到了:“递增式扩容你都告诉我了每次扩容增加一个单位的空间就最优了,那加倍式扩容每次增大几倍最优呢?”如果读者能发现这一点的话,真的非常棒啦!答案是2倍吗?当然不!那是几呢?真的有最优倍率吗?

一个视角:内存复用

如果倍率采用2甚至更大的数,那么被开辟过的旧空间永远都不会被新开辟的空间利用。小夕举个栗子。

if(倍率≥2){

那么以下是小夕为大家画的三次扩容后的内存块的占用情况

v2-622286981b0d4bedb9c437768177bfa8_b.png

上图中,内存块一共有15个字节。粉色实心框是数据结构占用的内存块,空心框是空闲的内存。

假如一开始数据结构的大小是1字节,占用了0xFF00这个字节,如图中第一列。然后第一次扩容后数据结构大小变成2字节,无法利用之前的旧内存空间。

同样,第二次扩容,第三次扩容后,数据结构的大小总是要比之前累计占用的旧内存空间之和还要大,总是大1个字节,所以永远都无法重新利用之前的旧内存空间。

那么无法复用旧内存空间,对应有程序与操作系统各有什么影响呢?小夕还没有探索出严谨的结论,读者有思路可以跟小夕一起讨论哦~

如果倍率改为比2大的数,结果是一样的。有兴趣的读者可以自行画画图~当然,数学好的喵喵不用画图也能证明出来的~(利用几何级数的性质)

}

if(倍率<2&&倍率>1){

比如倍率采用1.5。小夕再画一下图~

v2-ba61bc04e9a0cf7507961718547eb0d9_b.png

可以看到,第三次扩容后的新数据结构大小约为338B!而旧空间的大小是250+225=475>338,也就是说新的哈希表可以挪到旧的内存空间了!内存得到了复用!

好咯,说到这里,读者应该懂了,对于加倍式扩容,倍率必须小于2才能复用内存。那么为什么默认值取1.5,而不是1.6,1.7呢?小夕查了很多资料,发现这是一个启发式策略(启发式策略就是拍脑袋想出来的看似合理而没有严谨理论依据的方法)。

一个疑问

那么既然看似倍率用1.5要优于2,为什么C++中部分Vector的实现中却采用2呢?

注:感谢@冒泡 指正,有的C++中Vector的实现采用的1.5倍。

这就是理论与工程的不同之处。在工程中不仅要考虑内存复用这一个问题,还要考虑到浮点数运算问题和大量数据场景下的扩容速度的问题

关于浮点运算:此处感谢@谢天奇指正,小夕之前想当然了,深深抱歉!浮点运算速度不会因有效位的增加而降低,但一般来说浮点运算效率确实比整型运算的效率低。因此确实存在浮点运算问题,但若采用浮点数运算,计算浮点数时的速度在理论上是几乎无变化的。

扩容速度也很好理解。大量数据时,2倍扩容速度会比1.5倍扩容速度少很多次扩容次数,因此效率会比1.5倍高很多。那么当程序不怎么看重内存复用,却有大量数据待填入数据结构时,2倍是更合理的。

补充:关于哈希的扩容倍率

该章节由@冒泡提供,对哈希的扩容倍率的考虑不在上一章节讨论范围内。哈希之所以采用2倍的扩容倍率(更准确的说哈希的扩容倍率应采用2的幂次),是处于哈希表元素找位置的角度考虑的。

下面引用@冒泡清晰的讲解:

一般来说,hash表元素找位置的办法是元素的hash值对表大小取模
理论上表大小是个正数就可以,不过对于一般的数字,计算机的整数除法是很慢的
如果表大小是2的幂,则可以用位运算来代替除法,比如表大小为1024,则K%1024可以优化为K&0x3FF,速度就快很多,所以hash表大小最好保持为2的幂,因此扩容时候只能乘以2,或乘以2的幂
因为这个原因,java的hash表扩容,才是翻两倍

So

虽然很多数据结构都是基于静态数组实现的动态空间增长,但是有的是上述提到的2倍的扩容倍率甚至更高,有的像Java中的ArrayList和C++中Vector的部分实现中则为1.5倍的扩容倍率。

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

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

相关文章

量子计算机创造了虫洞吗?不完全是,但揭示了量子模拟的未来

来源&#xff1a;光子盒研究院最近&#xff0c;科学家们因据称产生了一个虫洞而成为头条新闻。发表在《自然》杂志上的这项研究使用量子计算机在简化的物理模型中模拟虫洞。消息传出后不久&#xff0c;物理学家和量子计算专家对虫洞是否真的存在表示怀疑。这是怎么回事&#xf…

复旦大学邱锡鹏教授:语言模型即服务,走向大模型的未来

来源&#xff1a;智源社区作者&#xff1a;智源社区整理&#xff1a;周致毅人类一直期待AI能在处理自然语言方面大放异彩&#xff0c;语言大模型在近些年已逐渐成为NLP领域的标配。随着模型的扩张&#xff0c;当前大模型的调用已变成上游厂商开放API供下游使用的模式。这一模式…

史上最萌最认真的机器学习/深度学习/模式识别入门指导手册(二)

谢谢你们。小夕昨晚又熬夜被发现了&#xff0c;谢谢对小夕的关心...有你们陪着&#xff0c;小夕很幸运。路人乙&#xff1a;“小夕&#xff0c;别人都说上完coursera上的那门机器学习课就已经入门机器学习了&#xff0c;可你又不这样认为。那你认为上完那门课&#xff0c;或者说…

计算机史上首篇教你从算法问题提炼算法思想的文章

路人丙&#xff1a;“小夕&#xff0c;你说学算法有什么用呢&#xff1f;”小夕&#xff1a;“好玩呀。”路人丙&#xff1a;“算法问题那么多&#xff0c;现查现用不就好了&#xff1f;”小夕&#xff1a;“好咯&#xff0c;既然你诚心诚意的问了~小夕就大发慈悲的震惊你(&…

美国能源部正式宣布核聚变里程碑式突破,专家点评商业化还需数10年

来源&#xff1a;澎拜责任编辑&#xff1a;郑洁图片编辑&#xff1a;张同泽校对&#xff1a;施鋆耗资35亿美元的美国国家点火装置用了10多年时间不断冲击点火目标&#xff0c;过程一波三折。成功点火后有两条路可走。一是冲刺更高的能量增益&#xff0c;未来或将再建更大能量的…

Science:找到杀死癌细胞的“单词”

来源&#xff1a;生物通利用新的机器学习技术&#xff0c;加州大学旧金山分校(UCSF)的研究人员与IBM研究院的一个团队合作&#xff0c;开发了一个包含数千个细胞“命令句”的虚拟分子库&#xff0c;基于引导工程免疫细胞寻找并不知疲倦地杀死癌细胞的“单词”组合。预测模型允许…

拼写校正与动态规划的小故事

喵喵喵&#xff0c;细心的你有没有发现小夕已经将卧室和书房精装修了呢~可以输入口令【ho】&#xff0c;或者点击主页的“旧的故事”标签进入哦。一个小现象小夕今天给大家讲一个自然语言处理/信息检索领域的小现象~细心的同学可能发现啦&#xff0c;每当你在使用某度进行搜索时…

《物理世界》公布2022年度十大突破

来源&#xff1a;科技日报近日&#xff0c;英国《物理世界》杂志公布了2022年度十大突破&#xff0c;涵盖从量子、医学物理学、天文学到凝聚态物质等各个方面。这十项突破是由《物理世界》编辑小组从今年在该杂志网站上发布的涵盖物理学所有领域的数百项研究中精选出来的。开创…

飞行器真的是计算机!

小夕在一边默默的玩弄心爱的小四轴&#xff08;飞行器&#xff09;。路人丁&#xff1a;“小夕&#xff0c;你不是学计算机的吗&#xff1f;怎么玩起飞机来了&#xff1f;”小夕&#xff1a;“对呀&#xff0c;小四轴也是计算机呀”。路人丁&#xff1a;诶&#xff1f;会不会有…

谷歌员工担心自家 AI 敌不过 ChatGPT,高管回应:其过快发展可能损害公司

来源&#xff1a;AI前线整理&#xff1a;冬梅、核子可乐谷歌的 Jeff Dean 表示&#xff0c;一旦提供错误信息、大企业所面临的“声誉风险”要比小公司更严重&#xff0c;所以谷歌自然“比小型初创公司更加保守”。自 11 月底以来&#xff0c;AI 聊天机器人 ChatGPT 正式向公众发…

深入浅出不可思议的中文分词技术

亲爱的喵喵们&#xff0c;明天小夕就正式开学啦。开学后每周有几天的课比较多&#xff0c;小夕为了保证每篇文章的质量&#xff0c;可能做不到每天一更啦。但是小夕也会尽量挤出时间来写文章&#xff0c;尽量保证两三天更一篇。希望不要责怪小夕哦...分词&#xff0c;也叫切词&…

史上最萌最认真的机器学习/深度学习/模式识别入门指导手册(三)

能够坚持走到阶段三的喵一定是很有毅力的喵&#xff01;也是很合格的机器学习/人工智能研究者啦~但是走到这里&#xff0c;也意味着您不满足于使用看似孤立的机器学习模型来生硬的完成一些任务&#xff0c;那么&#xff0c;大一统的理论体系就在这一阶段建立吧~前言完成这一阶段…

专访中国工程院院士杜祥琬,可控核聚变美国成了吗?

凤凰网科技 出品作者&#xff5c;蒋浇微信编辑&#xff5c;李金洋12月13日&#xff0c;美国能源部宣布&#xff0c;在加利福尼亚州的劳伦斯利弗莫尔国家实验室&#xff08;LLNL&#xff09;&#xff0c;研究人员首次在核聚变反应中产生“净能量增益”&#xff0c;即聚变反应产生…

一个问题就可以辨别真假NLP(自然语言处理)研究者

╮(╯▽╰)╭哎&#xff0c;自从人工智能火了以后&#xff0c;最近自称NLP研究者的人越来越多了&#xff0c;然而这其中的大忽悠有多少小夕就不想多说了。如果连自己在哪个领域、哪个学科搞研究都不懂的话&#xff0c;你相信他真的是这个领域的研究者喵&#xff1f;反正小夕不信…

2023年十大数字科技前沿应用趋势

来源&#xff1a;腾讯研究院编辑&#xff1a;蒲蒲继21年的“变量”、22年的“融合”&#xff0c;2023年数字科技的发展呈现出明显的“升维”特征。近日&#xff0c;腾讯研究院联合百位内部科学家、技术专家和外部院士专家&#xff0c;发布《升维 - 2023年十大数字科技前沿应用趋…

绕开数学,讲讲信息论

看到文章的标题&#xff0c;肯定有人想说“诶&#xff1f;小夕你不是做人工智能吗&#xff1f;怎么又捣鼓起通信里面的信息论了&#xff1f;”小夕肯定会很开心的揪住你&#xff0c;然后说“哈哈哈~发现一只外行&#xff01;”学过机器学习的喵喵肯定知道很多决策树的核心就是计…

大数据时代的机器学习有什么不同?

小夕昨晚没有发文章&#xff0c;却收到了一个大红包和好多晚安&#xff0c;好受之有愧..>_<...谢谢你们喵。路人戊&#xff1a;小夕&#xff0c;我们身处大数据时代了&#xff0c;好厉害呢。小夕&#xff1a;嗯嗯&#xff0c;哪里厉害了呢&#xff1f;路人戊&#xff1a;…

Science Bulletin | 阿尔茨海默病的现状与未来

目前全球人口老龄化进展严重&#xff0c;中国已成为世界上老龄化最快的国家之一。随着人口老龄化&#xff0c;痴呆患者的数目急剧增加&#xff0c;阿尔茨海默病&#xff08;Alzheimers disease, AD&#xff09;作为痴呆的主要原因&#xff0c;已成为当今社会最为热点的话题之一…

Science年度十大科学突破出炉:韦伯望远镜高居榜首,AIGC、NASA主动撞击小行星紧随其后...

来源&#xff1a;FUTURE远见选编&#xff1a;FUTURE | 远见 闵青云 文&#xff1a;量子位 前脚韦伯望远镜运维科学家刚获得Nature年度十大人物&#xff1b;紧接着&#xff0c;Science也将韦伯望远镜评为年度最大科学突破&#xff01;今天&#xff0c;Science重磅发布2022年度科…

词袋、独热与词向量

亲爱的读者喵喵&#xff0c;由于一些事情&#xff0c;小夕最近情绪欠佳&#xff0c;导致昨天又失约没有更新文章...而且文章中萌气散失...小夕会尽快调整好哦&#xff0c;小夕依然萌萌嗒我们知道&#xff0c;传统的数据挖掘任务面向的是结构化数据。结构化数据很好理解&#xf…