链表

36 阅读3分钟

链表以及数组的缺点

链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同。
数组的缺点:

  1. 数组的创建通常需要申请一段连续的内存空间(一整块的内存),并且大小是固定的(大多数编程语言数组都是固定的),所以当 当前数组不能满足容量需求时,需要扩容。 (一般情况下是申请一个更大的数组,比如2倍。 然后将原数组中的元素复制过去)
  2. 在数组开头或中间位置插入数据的成本很高,需要进行大量元素的位移。

QQ截图20240527081936.png

链表结构的封装

  1. 封装一个Node类,用于封装每一个节点上的信息(包括值和指向下一个节点的引用),它是一个泛型类。
  2. 封装一个LinkedList类,用于表示我们的链表结构。链表中我们保存两个属性,一个是链表的长度,一个是链表中第一个节点。
// 1.创建Node节点类
class Node<T> {
    value: T
    next: Node<T> | null = null
    constructor(value: T) {
        this.value = value
    }
}

// 2.创建LinkedList的类
class LinkedList<T> {
    head: Node<T> | null = null
    private size: number = 0

    get length() {
        return this.size
    }
}

append方法

向链表尾部追加数据可能有两种情况:\

  1. 链表本身为空,新添加的数据时唯一的节点。
  2. 链表不为空,需要向其他节点后面追加节点。 QQ截图20240527082919.png
    // 追加节点
    append(value: T) {
        // 1.根据value创建一个新节点
        const newNode = new Node(value)
        // 2.判断this.head是否为null
        if (!this.head) {
            this.head = newNode
        } else {
            let current = this.head
            while (current.next) {
                current = current.next
            }
            // current肯定是指向最后一个节点的
            current.next = newNode
        }
        // 3.size++
        this.size++
    }

链表的遍历方法(traverse)

    // 遍历链表的方法
    traverse() {
        const values: T[] = []
        let current = this.head
        while (current) {
            values.push(current.value)
            current = current.next
        }
        console.log(values.join("->"))
    }

insert方法

  1. 添加到第一个位置:添加到第一个位置,表示新添加的节点是头, 就需要将原来的头节点,作为新节点的next,另外这个时候的head应该指向新节点。 QQ截图20240527083321.png

  2. 添加到其他位置:如果是添加到其他位置,就需要先找到这个节点位置了。通过while循环,一点点向下找。 并且在这个过程中保存上一个节 点和下一个节点。找到正确的位置后,将新节点的next指向下一个节点,将上一个节点的 next指向新的节点。 QQ截图20240527083545.png

    // 插入方法: abc
    insert(value: T, position: number): boolean {
        // 1.越界的判断
        if (position < 0 || position > this.size) return false
        // 2.根据value创建新的节点
        const newNode = new Node(value)
        // 3.判断是否需要插入头部
        if (position === 0) {
            newNode.next = this.head
            this.head = newNode
        } else {
            let current = this.head
            let previous: Node<T> | null = null
            let index = 0
            while (index++ < position && current) {
                previous = current
                current = current.next
            }
            // index === position
            newNode.next = current
            previous!.next = newNode
        }
        this.size++
        return true
    }

removeAt方法

  1. 移除第一项的信息:直接让head指向第二项信息 QQ截图20240527083959.png
  2. 其他项的信息:通过while循环,找到正确的位置,找到正确位置后,就可以直接将上一项的next指向current 项的next。 QQ截图20240527084039.png
    // 删除方法:
    removeAt(position: number): T | null {
        // 1.越界的判断
        if (position < 0 || position >= this.size) return null
        // 2.判断是否是删除第一个节点
        let current = this.head
        if (position === 0) {
            this.head = current?.next ?? null
        } else {
            let previous: Node<T> | null = null
            let index = 0
            while (index++ < position && current) {
                previous = current
                current = current.next
            }
            // 找到需要的节点
            previous!.next = current?.next ?? null
        }
        this.size--
        return current?.value ?? null
    }

get方法

    // 获取方法:
    get(position: number): T | null {
        // 越界问题
        if (position < 0 || position >= this.size) return null
        // 2.查找元素, 并且范围元素
        let index = 0
        let current = this.head
        while (index++ < position && current) {
            current = current.next
        }
        // index === position
        return current?.value ?? null
    }