链表

156 阅读3分钟

链表区别与数据是,数组是连续的可以直接通过所用arr[0],arr[1],而链表的每一项都包含两部分的内容:数据域,指针域。js中的链表是以嵌套形式的对象来表示的:

{
    
    // 数据域
    val: 1,
    // 指针域,指向下一个结点
    next: {
        // 数据域
        val:2,
        // 指针域,指向下一个结点
        next: {
            val:3,
            next: ...
        }
    }
}   

数据域:当前节点的值,指针域:下一个节点的引用。所以有了指针记录下一个节点,所以每一个节点至少能知道他的下一个节点是谁了。

list.webp

为了确保起点结点是可抵达的,我们有时还会设定一个 head 指针来专门指向链表的开始位置:

head-list.webp

链表的创建

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);

image.png

create-list-node.webp

链表元素的添加

结合前面的学习,我们已经知道链表节点的关系是通过next来维系的,因此,链表的添加和删除都是围绕指针作文章那么添加的话,分为首添加,尾添加,中间添加,那么首尾添加相对来简单只用改变一个指针,首添加:只用讲当前节点的指针指向首节点。

add-end.webp

那么中间位置添加,需要改变两个指针,当前节点的指针指向前节点的指针(也就是后节点),再改变前节点的指针指向当前节点。

add-center-before.webp 修改后:

add-center.webp

用代码表示就是:

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);

image.png

链表元素的删除:

链表元素的删除也分为首尾删除,中间删除,首尾删除不用修改指针,中间删除,那么需要将前节点的指针指向后节点。

remove-center.webp

如此一来,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)

结论:

链表的插入/删除效率较高,而访问效率较低;数组的访问效率较高,而插入效率较低。