链表区别与数据是,数组是连续的可以直接通过所用arr[0],arr[1],而链表的每一项都包含两部分的内容:数据域,指针域。js中的链表是以嵌套形式的对象来表示的:
{
// 数据域
val: 1,
// 指针域,指向下一个结点
next: {
// 数据域
val:2,
// 指针域,指向下一个结点
next: {
val:3,
next: ...
}
}
}
数据域:当前节点的值,指针域:下一个节点的引用。所以有了指针记录下一个节点,所以每一个节点至少能知道他的下一个节点是谁了。
为了确保起点结点是可抵达的,我们有时还会设定一个 head 指针来专门指向链表的开始位置:
链表的创建
class CreateListNode<T> {
node: T
next: CreateListNode<T> | null
constructor(node: T) {
this.node = node
this.next = null
}
}
const node1 = new CreateListNode(1)
node1.next = new CreateListNode(2)
console.log('node1', node1);
链表元素的添加
结合前面的学习,我们已经知道链表节点的关系是通过next来维系的,因此,链表的添加和删除都是围绕指针作文章: 那么添加的话,分为首添加,尾添加,中间添加,那么首尾添加相对来简单只用改变一个指针,首添加:只用讲当前节点的指针指向首节点。
那么中间位置添加,需要改变两个指针,当前节点的指针指向前节点的指针(也就是后节点),再改变前节点的指针指向当前节点。
修改后:
用代码表示就是:
class CreateListNode<T> {
node: T
next: CreateListNode<T> | null
constructor(node: T) {
this.node = node
this.next = null
}
}
const node1 = new CreateListNode(1)
node1.next = new CreateListNode(2)
//1. 创建插入的节点
const node3 = new CreateListNode(3)
//2. 修改当前插入的节点的指针指向前节点的指针,也就是后节点
node3.next = node1.next
//3. 再修改前节点的指针指向当前插入的节点
node1.next = node3
console.log('node1', node1);
链表元素的删除:
链表元素的删除也分为首尾删除,中间删除,首尾删除不用修改指针,中间删除,那么需要将前节点的指针指向后节点。
如此一来,node3 就成为了一个完全不可抵达的结点了,它会被 JS 的垃圾回收器自动回收掉。这个过程用代码表述如下:
node1.next = node3.next
在涉及链表删除时,重点不是定位目标节点,而是目标节点的前节点,比如说咱们这个题里,其实只要能拿到 node1 就行了:
// 利用 node1 可以定位到 node3
const target = node1.next
node1.next = target.next
链表和数组的区别
在大多数的计算机语言中,数组都对应着一段连续的内存。如果我们想要在任意位置删除一个元素,那么该位置往后的所有元素,都需要往前挪一个位置;相应地,如果要在任意位置新增一个元素,那么该位置往后的所有元素也都要往后挪一个位置。
我们假设数组的长度是 n,那么因增加/删除操作导致需要移动的元素数量,就会随着数组长度 n 的增大而增大,呈一个线性关系。所以说数组增加/删除操作对应的复杂度就是 O(n) 。
但 JS 中不一定是。
JS比较特别。如果我们在一个数组中只定义了一种类型的元素,比如:
const arr = [1,2,3,4]
它是一个纯数字数组,那么对应的确实是连续内存。
但如果我们定义了不同类型的元素:
const arr = ['haha', 1, {a:1}]
它对应的就是一段非连续的内存。此时,JS 数组不再具有数组的特征,其底层使用哈希映射分配内存空间,是由对象链表来实现的。 相对于数组来说,链表有一个明显的优点,就是添加和删除元素都不需要挪动多余的元素,时间复杂度为O(1)。
结论:
链表的插入/删除效率较高,而访问效率较低;数组的访问效率较高,而插入效率较低。