映射表map(平衡二叉树实现)_集合与映射

今天我们来聊聊集合(Set)和映射(Map),由于这两种数据结构相对比较简单,我们用一章的篇幅已经可以说明白了。相信很多人都对集合和映射比较熟悉了,这篇文章我们不准备花太多篇幅来讲概念性的东西,我们主要分析一下集合与映射的时间复杂度,使用不同数据结构实现之间的差别。这篇文章主要是为了整个系列内容的完整性。所以,如果你觉得自己对集合与映射已经非常熟悉了,那你可以出门左拐,不用浪费时间了。 

集合(Set)

首先,我们来看一下集合。一个班的学生是一个集合,咱们国家的所有学校是一个集合,上海市所有地铁站点是一个集合等等。从上面的例子我们可以总结出,集合就是一组有着相同属性的事物,这些事物都有一个唯一的标识。例如,一个班的学生每个人都有唯一的身份证编号,通过这个编号我们就能够判断出一个人是否在班级内。

说了这么多,集合到底有什么用呢?这里我们还是以Redis为例,在Redis中有两种集合类型的数据结构无序集合(Set)有序集合(Zset)。假如我们现在要使用Redis的集合实现记录一篇文章所有点赞人的信息,我们可以设置一个以文章ID为key的集合,这里我们使用Redis的无序集合,如图:

1228307ed9d68337d932509a02e218cf.png

我们看到上面的用户ID就是一个集合,当我们需要找出这篇文章所有点赞人的信息,我们只需要将这个集合里的用户ID取出来就可以了。

再进一步,假如我们在显示文章的时候,要将点赞人以列表的方式以点赞时间倒序的方式显示,这时候我们可以使用Redis的Zset,Zset在往集合里添加元素的时候,会指定一个score,这个score就是排序的分值,在往有序集合里添加数据的时候会比较当前元素的score的值并放到合适的位置,假设我们使用数组或者链表来实现,最坏的情况下,我们需要遍历一遍整个数组或者链表,而且如果使用数组作为底层实现,我们还要考虑数据搬移的情况。所以,在Redis中使用了一种更为高效的数据结构跳表来作为有序列表的底层实现之一。关于跳表我们在链表那节有做说明。后面如果有机会,我也会专门用一篇来聊一聊跳表这种数据结构,这里不再展开。

 其实,除了Redis以外,我们使用的MySQL,Mongo也使用到了集合这种数据结构,一张表就是一个集合,每一行数据就是这个集合里的一个元素。可以看到,当我们学习了数据结构之后就可以更加直观的认识各种优秀软件的本质,理解其中的原理。当然,数据库的实现远比我们说的要复杂,这里只是作为一个延伸,有兴趣的话可以去看一下数据库实现的思路及原理。

下面,我们来实现一个集合,我们使用链表作为底层实现,这里我们只实现无序集合,理解了无序集合的实现,相信实现一个有序集合也是很简单的。对应的代码你可以点击链接进行查看:

https://github.com/seepre/data-structure/blob/java/Set/src/LinkListSet.java

具体实现底层数据结构是使用了我们前面讲到的链表。实际上,我们通过链表实现的集合就是对链表的增删查操作。

映射(Map)

我假设大家都知道Map是什么并且知道怎么用。映射又叫Map,在PHP、JavaScript等脚本语言中也叫关联数组,Map可以说是我们平时工作中最常用的数据结构了。但是,遗憾的是,在我多次面试的经历中,很多候选人答不上来Map的随机访问时间复杂度是多少。今天,我们就从一个问题切入,问:Map的随机访问时间复杂度是多少?

这个问题的答案并不是唯一的,Map也可以有多种实现方式,比较经典的实现方式是使用我们上一篇讲到的散列表,我们可以给Map的key计算一个hash值,这样我们就通过这个hash值在key和值之间建立起了关联关系,我们知道散列表的时间复杂度是O(1)。所以,使用散表实现的Map的随机访问时间复杂度也是O(1)。

当然,Map还可以使用数组,链表,树结构来实现。假如我们使用数组或者链表来实现一个Map,其随机访问一个键的值的时候,我们需要遍历数据结构的每一个元素,对比其中的Key是否和我们要查找的Key相等,如果相等,返回当前结点对应的值。在这种情况下,最坏的情况,我们需要将整个数据结构遍历一遍才能找到我们要的数据。回忆一下,我们的数组,链表的随机访问时间复杂度是多少?是O(N),那么对于map来讲,使用数组和链表实现的Map的随机访问时间复杂度也是O(N)。那么,如果我们要添加一个元素呢?答案是和数组或者链表的插入时间复杂度是一样的,对于数组来说,由于涉及到数组的搬移是O(N)。对于链表来说,由于没有数组搬移的操作,时间复杂度就是O(1)。由于我们这里还没讲到树这种数据结构,这里我们就不对树实现的Map做时间复杂分析了,后面在讲到树形数据结构的时候,我们会再详细分析。

下面,我们使用前面说到的链表来实现一个基于链表的Map,核心代码你可以点击链表查看:

https://github.com/seepre/data-structure/blob/java/Map/src/LinkListMap.java

如果你有兴趣,你可以使用我们上一节讲的散列表自己来实现一个基于散列的Map。

总结一下

集合与映射在我们实际工作中是时时刻刻都在使用的数据结构,比如Jave里的Map,PHP里的关联数组,Python里的list和dict,只是对于语言使用者来说,这些数据结构已经在底层封装好了。

很多人在写代码的时候,总觉得有一层东西挡在前面看不透,对自己写的代码没底,其实就是对数据结构理解得不够透彻。数据结构与算法需要大量的时间学习,很多人觉得平时工作当中并没不会从零手写一个散列表,也不会从头开始实现一个Map。我觉得这个需要从两个方面认识这个事情。第一,如果你大部分是在写业务,确实这些数据结构可能你的职业生涯里用到的机率都很小。但是,数据结构和算法是搭建业务和技术的一座桥梁,只有熟悉理解了数据结构与算法,才能更好的设计业务,将技术更好的应用在业务上,那些牛逼的工程师总是能写出设计优雅,扩展性好的代码其秘诀其实就掌握了数据结构与算法。第二,如果你想往架构方向或者中间件开发方向发展,那么数据结构与算法一定是你需要逾越的鸿沟,比如,我们前面说的限流算法,缓存淘汰算法,都是最基本的东西,而这些是需要结合业务的,很多时候在网上是没有现成的解决方案的。

下一节,我们讲一个非常重要的树形数据结构——二分搜索树

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值