【数据结构】-链表结构

96 阅读5分钟

链表(Linked List)是一种常见的线性数据结构,它通过指针将一系列的数据节点连接在一起。与数组相比,链表在插入和删除元素时更具优势,因为它不需要移动其他元素

基本概念

链表是一种动态数据结构,其长度可以根据需要进行扩展或缩减。链表中的每个数据节点包含两个部分:数据域和指针域。数据域用于存储数据,指针域用于存储指向下一个节点的指针。根据指针域的不同,链表可分为单向链表、双向链表和循环链表

创建链表时,首先需要定义一个节点结构,包含数据域和指针域。然后创建一个头节点,用于存储链表的起始地址。

链表结构

  1. 节点(Node):链表的基本单位,包含数据和指向下一个节点的指针。

  2. 指针(Pointer):用于连接节点,指示下一个节点的位置。

  3. 头节点(Head):链表的起始节点,通常用于访问链表中的第一个元素。

  4. 尾节点(Tail):链表的最后一个节点,其指针指向空值(NULL)。

节点代码示例:

class Node<T> {    data: T;    next: Node<T> | null;    constructor(data: T) {        this.data = data;        this.next = null;    }}

链表类型

  1. 「单向链表」

向链表(Singly Linked List)是最简单的链表结构,每个节点只有一个指针域,指向其后继节点。单向链表的最后一个节点的指针域为空,表示链表的结束。

class Node<T> {    data: T;    next: Node<T> | null;    prev: Node<T> | null;    constructor(data: T) {        this.data = data;        this.next = null;        this.prev = null;    }}class DoublyLinkedList<T> {    head: Node<T> | null;    tail: Node<T> | null;    constructor() {        this.head = null;        this.tail = null;    }    append(data: T): void {        const newNode = new Node(data);        if (!this.head) {            this.head = newNode;            this.tail = newNode;            return;        }        newNode.prev = this.tail;        this.tail!.next = newNode;        this.tail = newNode;    }    print(): void {        let current = this.head;        while (current) {            console.log(current.data);            current = current.next;        }    }}const doublyLinkedList = new DoublyLinkedList<number>();doublyLinkedList.append(1);doublyLinkedList.append(2);doublyLinkedList.append(3);doublyLinkedList.print(); // 输出:1 2 3
  1. 「双向链表」

双向链表(Doubly Linked List)中的每个节点有两个指针域,一个指向后继节点,另一个指向前驱节点。双向链表的头节点的前驱指针和尾节点的后继指针都为空。

class Node<T> {    data: T;    next: Node<T> | null;    prev: Node<T> | null;    constructor(data: T) {        this.data = data;        this.next = null;        this.prev = null;    }}class DoublyLinkedList<T> {    head: Node<T> | null;    tail: Node<T> | null;    constructor() {        this.head = null;        this.tail = null;    }    append(data: T): void {        const newNode = new Node(data);        if (!this.head) {            this.head = newNode;            this.tail = newNode;            return;        }        newNode.prev = this.tail;        this.tail!.next = newNode;        this.tail = newNode;    }    print(): void {        let current = this.head;        while (current) {            console.log(current.data);            current = current.next;        }    }}const doublyLinkedList = new DoublyLinkedList<number>();doublyLinkedList.append(1);doublyLinkedList.append(2);doublyLinkedList.append(3);doublyLinkedList.print(); // 输出:1 2 3
  1. 「循环链表」 循环链表(Circular Linked List)是一种特殊的链表结构,其中最后一个节点的指针域指向头节点,形成一个环形结构。循环链表可以是单向的,也可以是双向的。

    class Node { data: T; next: Node | null; constructor(data: T) { this.data = data; this.next = null; }}class CircularLinkedList { head: Node | null; constructor() { this.head = null; } append(data: T): void { const newNode = new Node(data); if (!this.head) { this.head = newNode; newNode.next = this.head; return; } let current = this.head; while (current.next !== this.head) { current = current.next!; } current.next = newNode; newNode.next = this.head; } print(): void { let current = this.head; if (!current) return; do { console.log(current.data); current = current.next!; } while (current !== this.head); }}const circularLinkedList = new CircularLinkedList();circularLinkedList.append(1);circularLinkedList.append(2);circularLinkedList.append(3);circularLinkedList.print(); // 输出:1 2 3 1 2 3 ...

基本操作

  1. 「插入(Insertion)」:在链表中插入节点时,需要先找到插入位置的前驱节点。然后将新节点的指针域指向前驱节点的后继节点,再将前驱节点的指针域指向新节点。对于双向链表,还需要将新节点的前驱指针域指向前驱节点,后继节点的前驱指针域指向新节点

  2. 「删除(Deletion)」:删除链表中的节点时,首先需要找到待删除节点的前驱节点。然后将前驱节点的指针域指向待删除节点的后继节点。对于双向链表,还需要将后继节点的前驱指针域指向前驱节点。最后释放待删除节点的内存空间。

  3. 「查找(Search)」:查找链表中的节点通常需要遍历链表,根据数据域的值或者其他条件进行查找。找到目标节点后,可以返回其地址或者其他相关信息。

  4. 「遍历(Traversal)」:遍历链表是访问链表中每个节点的过程。从头节点开始,通过指针域依次访问每个节点,直到链表结束。遍历链表时可以执行各种操作,如打印节点数据、修改节点数据等。

单向链表实现链表的插入、删除、遍历和查找操作

// 单向链表节点类class SinglyNode<T> {    data: T;    next: SinglyNode<T> | null;    constructor(data: T) {        this.data = data;        this.next = null;    }}// 单向链表类class SinglyLinkedList<T> {    head: SinglyNode<T> | null;    constructor() {        this.head = null;    }    // 插入操作    insert(data: T, position: number): void {        const newNode = new SinglyNode(data);        if (position < 0) return;        if (position === 0) {            newNode.next = this.head;            this.head = newNode;            return;        }        let current = this.head;        let index = 0;        while (current && index< position - 1) {            current = current.next;            index++;        }        if (!current) return;        newNode.next = current.next;        current.next = newNode;    }    // 删除操作    remove(position: number): void {        if (position < 0 || !this.head) return;        if (position === 0) {            this.head = this.head.next;            return;        }        let current = this.head;        let index = 0;        while (current.next && index< position - 1) {            current = current.next;            index++;        }        if (!current.next) return;        current.next = current.next.next;    }    // 遍历操作    traverse(callback: (data: T) => void): void {        let current = this.head;        while (current) {            callback(current.data);            current = current.next;        }    }    // 查找操作    find(data: T): SinglyNode<T> | null {        let current = this.head;        while (current) {            if (current.data === data) {                return current;            }            current = current.next;        }        return null;    }}// 示例const singlyLinkedList = new SinglyLinkedList<number>();singlyLinkedList.insert(1, 0);singlyLinkedList.insert(2, 1);singlyLinkedList.insert(3, 2);singlyLinkedList.traverse((data) => {    console.log(data); // 输出:1 2 3});singlyLinkedList.remove(1);singlyLinkedList.traverse((data) => {    console.log(data); // 输出:1 3});const foundNode = singlyLinkedList.find(3);console.log(foundNode?.data); // 输出:3

双向链表实现链表的插入、删除、遍历和查找操作

// 双向链表节点类class DoublyNode<T> {    data: T;    next: DoublyNode<T> | null;    prev: DoublyNode<T> | null;    constructor(data: T) {        this.data = data;        this.next = null;        this.prev = null;    }}// 双向链表类class DoublyLinkedList<T> {    head: DoublyNode<T> | null;    tail: DoublyNode<T> | null;    constructor() {        this.head = null;        this.tail = null;    }    // 插入操作    insert(data: T, position: number): void {        const newNode = new DoublyNode(data);        if (position < 0) return;        if (position === 0) {            newNode.next = this.head;            if (this.head) {                this.head.prev = newNode;            }            this.head = newNode;            if (!this.tail) {                this.tail = newNode;            }            return;        }        let current = this.head;        let index = 0;        while (current && index< position - 1) {            current = current.next;            index++;        }        if (!current) return;        newNode.next = current.next;        newNode.prev = current;        if (current.next) {            current.next.prev = newNode;        } else {            this.tail = newNode;        }        current.next = newNode;    }    // 删除操作    remove(position: number): void {        if (position < 0 || !this.head) return;        if (position === 0) {            this.head = this.head.next;            if (this.head) {                this.head.prev = null;            } else {                this.tail = null;            }            return;        }        let current = this.head;        let index = 0;        while (current.next && index< position - 1) {            current = current.next;            index++;        }        if (!current.next) return;        const removedNode = current.next;        current.next = current.next.next;        if (current.next) {            current.next.prev = current;        } else {            this.tail = current;        }    }    // 遍历操作    traverse(callback: (data: T) => void): void {        let current = this.head;        while (current) {            callback(current.data);            current = current.next;        }    }    // 查找操作    find(data: T): DoublyNode<T> | null {        let current = this.head;        while (current) {            if (current.data === data) {                return current;            }            current = current.next;        }        return null;    }}const doublyLinkedList = new DoublyLinkedList<number>();doublyLinkedList.insert(1, 0);doublyLinkedList.insert(2, 1);doublyLinkedList.insert(3, 2);doublyLinkedList.traverse((data) => {    console.log(data); // 输出:1 2 3});doublyLinkedList.remove(1);doublyLinkedList.traverse((data) => {    console.log(data); // 输出:1 3});const foundDoublyNode = doublyLinkedList.find(3);console.log(foundDoublyNode?.data); // 输出:3

应用场景

  1. 「动态内存管理」:链表被广泛用于操作系统的动态内存管理。操作系统使用链表来跟踪可用的内存块和已分配的内存块

  2. 「文件系统的实现」:在文件系统中,链表用于存储文件的各个部分的位置信息。这使得文件可以分散存储在磁盘的不同部分。

  3. 「图的表示」:在计算机科学中,链表是表示图的两种主要方式之一。链表可以用来表示图中的顶点和边

  4. 「实现高级数据结构」:链表还可以用来实现其他高级数据结构,如堆栈、队列和哈希表。