帮你把链表操作学个通透!

653 阅读4分钟

帮你把链表操作学个通透!

前言

链表是大家在大学数据结构课程中首先接触到的结构。对于许多人而言,它是打开数据结构这扇神秘大门的钥匙。我到现在都记忆深刻那个场景,老师在台上画链表,当时虽然没听太懂,但是也算是我数据结构的启蒙课了!

在这篇文章中,我们将一起回顾和学习链表的基本概念、操作方法和应用场景。希望通过本文,你能够对链表有一个全面而深入的理解,并能够在实际编程中熟练运用。

什么是链表?

链表是一种线性数据结构,但与数组不同的是,链表的元素在内存中不是连续存储的。每个元素(称为节点)包含两部分:数据和指向下一个节点的指针。这种结构使得链表在插入和删除操作上具有很高的灵活性。

链表的基本结构

链表的基本结构由节点组成,每个节点包含两个部分:

  1. 数据域:存储数据。
  2. 指针域:存储指向下一个节点的地址。

一个简单的链表节点可以用以下结构表示(以 JavaScript 为例):

class ListNode {
    constructor(val, next = null) {
        this.val = val;
        this.next = next;
    }
}

单链表 双链表 循环链表

单链表:

它是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链表的入口节点称为链表的头结点也就是head。。

image.png

双链表:

双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。

双链表 既可以向前查询也可以向后查询。

image.png

循环链表

循环链表,顾名思义,就是链表首尾相连。循环链表可以用来解决约瑟夫环问题。

image.png

链表的定义

许多同学在面试时遇到的一个常见问题是无法正确地定义链表节点。这主要是因为他们在平时刷题时,LeetCode等平台通常已经为他们提供了现成的链表节点定义,他们只需要直接使用即可。因此,当面试官要求他们从头开始定义链表节点时,他们往往会出现各种错误。下面是JS语言版本

class ListNode {
  public val: number;
  public next: ListNode|null = null;
  constructor(value: number) {
    this.val = value;
    this.next = null;
  }
}
基本操作
1. 创建链表

创建一个链表可以通过连接多个节点来实现。例如:

const node1 = new ListNode(1);
const node2 = new ListNode(2);
const node3 = new ListNode(3);

node1.next = node2;
node2.next = node3;
2. 遍历链表

遍历链表是指从头节点开始,依次访问每个节点,直到到达链表的末尾。

function printList(head) {
    let current = head;
    while (current !== null) {
        console.log(current.val);
        current = current.next;
    }
}
3. 插入节点

在链表中插入节点可以分为三种情况:

  • 在头部插入:将新节点插入到链表的头部。
  • 在中间插入:将新节点插入到链表的某个位置。
  • 在尾部插入:将新节点插入到链表的尾部。

image.png

function insertAtHead(head, val) {
    const newNode = new ListNode(val);
    newNode.next = head;
    return newNode;
}

function insertAtPosition(head, val, position) {
    const newNode = new ListNode(val);
    if (position === 0) {
        newNode.next = head;
        return newNode;
    }
    let current = head;
    for (let i = 0; i < position - 1 && current !== null; i++) {
        current = current.next;
    }
    if (current !== null) {
        newNode.next = current.next;
        current.next = newNode;
    }
    return head;
}

function insertAtTail(head, val) {
    const newNode = new ListNode(val);
    if (head === null) {
        return newNode;
    }
    let current = head;
    while (current.next !== null) {
        current = current.next;
    }
    current.next = newNode;
    return head;
}
4. 删除节点

在链表中删除节点也可以分为三种情况:

  • 删除头部节点:将链表的头部节点删除。
  • 删除中间节点:将链表的某个位置的节点删除。
  • 删除尾部节点:将链表的尾部节点删除。

image.png

function deleteAtHead(head) {
    if (head === null) {
        return null;
    }
    return head.next;
}

function deleteAtPosition(head, position) {
    if (head === null) {
        return null;
    }
    if (position === 0) {
        return head.next;
    }
    let current = head;
    for (let i = 0; i < position - 1 && current !== null; i++) {
        current = current.next;
    }
    if (current !== null && current.next !== null) {
        current.next = current.next.next;
    }
    return head;
}

function deleteAtTail(head) {
    if (head === null) {
        return null;
    }
    if (head.next === null) {
        return null;
    }
    let current = head;
    while (current.next.next !== null) {
        current = current.next;
    }
    current.next = null;
    return head;
}
应用场景

链表在许多实际应用中都非常有用,例如:

  • 内存管理:操作系统中的内存管理使用链表来管理空闲内存块。
  • 动态数据结构:链表可以动态地增加或减少节点,适用于需要频繁插入和删除的场景。
  • 队列和栈:可以使用链表实现队列和栈数据结构。
总结

通过本文的学习,我们一起解了链表的类型、基本概念、节点结构、基本操作以及应用场景。链表作为一种灵活的数据结构,在许多实际问题中都有着广泛的应用。希望这篇文章能够帮助你对链表有一个全面而深入的理解,并能够在实际编程中熟练运用。

如果你有任何疑问或需要进一步的解释,请随时留言交流。祝你学习愉快!