在说链表之前,先来康康基本数据结构--数组--
数组的特性以及优缺点
数组:在计算机科学中,数组数据结构,简称数组,是由相同类型的元素的集合所组成的数据结构,分配一块连续的内存来存储。利用元素的索引可以计算出该元素对应的存储地址。 最简单的数据结构类型是一维数组。数组-维基百科
数组的特性:
- 存储在物理结构上是连续的。数组所占用的内存空间必须是连续的,不能由两个或多个内存碎片存储。
- 底层的数组长度是不可变的。为什么说是底层的数组?因为JavaScript这类特殊的语言,像Java、Python等语言中声明数组时必须指定数组的长度,并且指定长度之后长度不可变化,如果对超过数组长度的内存进行操作会发生数组越界异常;而JavaScript中在声明数组时可以不指定数组长度,并且可以随意操作数组(添加、删除),原因是js引擎在数组长度不够时进行了数组扩容。
数组扩容:当数组长度不够用需要扩容时,此时需要系统重新分配一块扩容后长度的物理空间,然后将扩容前的数组元素复制到新的物理空间中,这个过程是消耗性能的。尤其是在数据量较大时,所以尽可能避免发生数组扩容。
- 数组的变量指向了数组的第一个元素。比如声明一个数组
arr,那么arr其实指向的是数组的第一项的内存地址,由于在内存中数组是连续的,可以通过数组的第一项来访问整个数组中的所有元素。
数组的优点:
- 查询性能好,在查询某个位置的元素时尤为明显,由于数组在内存地址中是连续的,并且我们通过
arr[2]访问数组中的某个元素时,arr[n]其中的n其实为内存地址的偏移量,在操作系统中通过偏移量来进行查询效率是最高的。
数组的缺点:
- 由于数组存储必须使用连续的物理空间,那么在数据量较大或者系统空间碎片较多时不易存储。
- 由于数组长度定长,在对数组进行操作(插入、删除)时会带来性能的损耗。当在数组中间的某个部位插入或者删除一个元素时,此时需要将该位置后面的所有元素进行后移/前移,当数据量较大时性能损耗较大。
什么是链表
链表:链表是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。链表-维基百科
链表有单链表、双链表、循环链表,这里只介绍单链表。
使用JavaScript来创建一个简单的链表结构:
function Node(value) {
this.value = value;
this.next = null;
}
let node1 = new Node(1);
let node2 = new Node(2);
let node3 = new Node(3);
let node4 = new Node(4);
let node5 = new Node(5);
let head = node1;
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
链表的特性:
- 在空间上不是连续的。
- 每存放一个值都会浪费一个引用空间。
注:
- 传递链表必须传递链表的根节点(head)。
- 每一个节点都认为自己为根节点,不包含上一个节点的任何信息。
链表的优点:
- 只要内存足够大,不用担心空间碎片的问题。
- 链表的添加删除非常容易(只需更改引用即可)。 比如在节点b和节点c之间想插入一个节点d,只需将节点b的引用指向节点d,然后将节点d的引用指向节点c即可。
链表的缺点:
- 查询速度慢, 查询某个节点或者特定编号节点需要O(n)的时间
- 会浪费一些空间。 每添加一个节点都需要创建一个引用来指向下一个节点(当节点内的数据量越大时这部分开销对内存的影响越小)