链表结构

213 阅读4分钟

链表结构

链表(Linked List)是一种常见的线性数据结构,用于存储和组织数据。与数组不同,链表中的元素在内存中可以不连续存储,而是通过每个元素中的指针链接彼此。

链表由一系列节点(Node)组成,每个节点包含两部分:数据域(存储数据的部分)和指针域(指向下一个节点的指针)。数据域可以存储任意类型的数据,而指针域用于链接下一个节点。

常见的链表类型包括单向链表、双向链表和循环链表:

  1. 单向链表(Singly Linked List):每个节点只有一个指针,指向下一个节点。最后一个节点的指针指向空值(null)表示链表的结束。

image.png

与数组的不同

  • 要存储多个元素,数组(或选择链表)可能是最常用的数据结构。一般编程语言都有默认实现数组结构。

但是数组也有很多缺点:

  • 数组的创建通常需要申请一段连续的内存空间(一整块的内存),并且大小是固定的(大多数编程语言数组都是固定的),所以当当前数组不能满足容量需求时,需要扩容。 (一般情况下是申请一个更大的数组,比如2倍。 然后将原数组中的元素复制过去)

  • 而且在数组开头或中间位置插入数据的成本很高,需要进行大量元素的位移。

  • 尽管JavaScript的Array底层可以帮我们做这些事,但背后的原理依然是这样。

链表的优势

要存储多个元素,另外一个选择就是 链表

  • 内存空间不是必须连续的。 可以充分利用计算机的内存,实现灵活的内存动态管理。

  • 链表不必在创建时就确定大小,并且大小可以无限的延伸下去。

  • 链表在插入和删除数据时,时间复杂度可以达到O(1)。 相对数组效率高很多。

相对于数组,链表缺点:

  • 随机访问低效:链表中的元素并不是连续存储的,因此无法通过索引直接访问元素,需要从头节点开始遍历,直到找到目标节点,这导致随机访问的效率较低。
  • 额外的空间开销:链表需要额外的指针来存储节点之间的链接关系,这增加了一定的空间开销。
  • 不支持常数时间的随机访问和索引操作:与数组不同,链表不支持在常数时间内根据索引访问元素,需要遍历链表直到目标位置。

封装链表


// 创建 Node节点

class Node<T> {

    // 定义value 以及 next方法

    value: T | null = null;

    next: Node<T> | null = null;

    constructor(value: T) {

    this.value = value;

}
// 创建 LinkedList 的类

class LinkedList<T> {

    head: Node<T> | null = null;

    private size: number = 0;

    get length() {

    return this.size;

}

链表中常见的方法

链表中常用的方法


append(element):向链表尾部添加一个新的项

insert(position,element):向链表的特定位置插入一个新的项。

get(position) :获取对应位置的元素

indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返回-1。 

update(position,element) :修改某个位置的元素

removeAt(position):从链表的特定位置移除一项。

remove(element):从链表中移除一项。

isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。 

size():返回链表包含的元素个数。与数组的length属性类似。

链表的遍历方法(traverse)

为了可以方便的看到链表上的每一个元素,我们实现一个遍历链表每一个元素的方法:

  • 这个方法首先将当前结点设置为链表的头结点。

  • 然后,在while循环中,我们遍历链表并打印当前结点的数据。

  • 在每次迭代中,我们将当前结点设置为其下一个结点,直到遍历完整个链表。

traverse() {

    const values: T[] = [];

    let current = this.head;

    while (current) {

    values.push(current.value!);

    current = current.next;

    }
    console.log(values.join("->"));

}