本文发布于微信平台: 程序员面试官前端
超过20w字的「前端面试与进阶指南」能够移步githubnode
对于很多开发者而言,链表(linked list)这种数据结构既熟悉又陌生,熟悉是由于它确实是很是基础的数据结构,陌生的缘由是咱们在业务开发中用到它的概率的确不大.git
在不少状况下,咱们用数组就能很好的完成工做,并且不会产生太多的差别,那么链表存在的意义是什么?链表相比于数组有什么优点或者不足吗?程序员
链表是一种常见的基础数据结构,是一种线性表,可是并不会按线性的顺序存储数据,而是在每个节点里存到下一个节点的指针(Pointer).github
从本质上来说,链表与数组的确有类似之处,他们的相同点是都是线性数据结构,这与树和图不一样,而它们的不一样之处在于数组是一块连续的内存,而链表能够不是连续内存,链表的节点与节点之间经过指针来联系.面试
固然,链表也有不一样的形态,主要分为三种:单向链表、双向链表、循环链表.算法
单向链表的节点一般由两个部分构成,一个是节点储存的值val
,另外一个就是节点的指针next
.小程序
链表与数组相似,也能够进行查找、插入、删除、读取等操做,可是因为链表与数组的特性不一样,致使不一样操做的复杂度也不一样.微信小程序
单向链表的查找操做一般是这样的:设计模式
链表的查找性能与数组同样,都是时间复杂度为O(n).
链表与数组最大的不一样就在于删除、插入的性能优点,因为链表是非连续的内存,因此不用像数组同样在插入、删除操做的时候须要进行大面积的成员位移,好比在a、b节点之间插入一个新节点c,链表只须要:
这个插入操做仅仅须要移动一下指针便可,插入、删除的时间复杂度只有O(1).
链表的插入操做以下:
链表的删除操做以下:
链表相比之下也有劣势,那就是读取操做远不如数组,数组的读取操做之因此高效,是由于它是一块连续内存,数组的读取能够经过寻址公式快速定位,而链表因为非连续内存,因此必须经过指针一个一个节点遍历.
好比,对于一个数组,咱们要读取第三个成员,咱们仅须要arr[2]
就能快速获取成员,而链表则须要从头部节点进入,在经过指针进入后续节点才能读取.
因为双向链表的存在,单向链表的应用场景比较少,由于不少场景双向链表能够更出色地完成.
可是单向链表并不是无用武之地,在如下场景中依然会有单向链表的身影:
咱们上文已经提到,单向链表的应用场景并很少,而真正在生产环境中被普遍运用的正是双向链表.
双向链表与单向链表相比有何特殊之处?
咱们看到双向链表多了一个新的指针prev
指向节点的前一个节点,固然因为多了一个指针,因此双向链表要更占内存.
别小看双向链表多了一个前置指针,在不少场景里因为多了这个指针,它的效率更高,也更加实用.
好比编辑器的「undo/redo」操做,双向链表就更加适用,因为拥有先后指针,用户能够自由得进行先后操做,若是这个是一个单向链表,那么用户须要遍历链表这时的时间复杂度是O(n).
真正生产级应用中的编辑器采用的数据结构和设计模式更加复杂,好比Word就是采用Piece Table数据结构加上Command queue模式实现「undo/redo」的,不过这是后话了.
循环链表,顾名思义,他就是将单向链表的尾部指针指向了链表头节点:
循环链表一个应用场景就是操做系统的分时问题,好比有一台计算机,可是有多个用户使用,CPU要处理多个用户的请求极可能会出现抢占资源的状况,这个时候计算机会采起分时策略来保证每一个用户的使用体验.
每一个用户均可以当作循环链表上的节点,CPU会给每一个节点分配必定的处理时间,在必定的处理时间后进入下一个节点,而后无限循环,这样能够保证每一个用户的体验,不会出现一个用户抢占CPU而致使其余用户没法响应的状况.
固然,约瑟夫环的问题是单向循环链表的表明性应用,感兴趣的能够深刻了解下.
固然若是是双向链表首尾相接呢?这就是双向循环链表.
在Node中有一类场景,没有查询,可是却有大量的插入和删除,这就是Timer模块。 几乎全部的网络I/O请求,都会提供timeout操做控制socket的超时情况,这里就会大量使用到setTimeout,而且这些timeout定时器,绝大部分都是用不到的(数据按时正常响应),那么又会有响应的大量clearTimeout操做,所以node采用了双向循环链表来提升Timer模块的性能,至于其中的细节就再也不赘述了.
插入!
TimersList <-----> timer1 <-----> timer2 <-----> timer4 <-----> timer3 <-----> ......
1000ms后执行 1050ms后执行 1100ms后执行 1200ms后执行
复制代码
至此,咱们对链表这个数据结构有了必定的认知,因为其非连续内存的特性致使链表很是适用于频繁插入、删除的场景,而不见长于读取场景,这跟数组的特性刚好造成互补,因此如今也能够回到题目中的问题了,链表的特性与数组互补,各有所长,并且链表因为指针的存在能够造成环形链表,在特定场景也很是有用,所以链表的存在是颇有必要的。
那么,如今有一个很是常见的一个面试向的思考题:
咱们平时在用的微信小程序会有最近使用的功能,时间最近的在最上面,按照时间顺序日后排,当用过的小程序大于必定数量后,最不经常使用的小程序就不会出现了,你会如何设计这个算法?