数据结构-链表

链表是一种线性数据结构,它由一系列节点组成。每个节点包含两部分信息:

1.  **数据 (Data)**:存储节点所携带的实际数据,可以是任何类型的数据(整数、字符串、对象等)。
2.  **指针 (Pointer) / 链接 (Link) / 引用 (Reference)**:指向链表中的下一个节点。  对于最后一个节点,指针指向 `NULL` (或者其他表示空引用的值, 比如 `nullptr` 或者 `0` ),表示链表的结束。

**链表的类型**

链表可以根据不同的特性进行分类,主要的有几种:

1.  **单链表 (Singly Linked List)**:
    *   每个节点只有一个指针,指向下一个节点。
    *   只能单向遍历,从头节点向尾节点。
    *   最简单的链表。

    ```
    Head -> [Data | Next] -> [Data | Next] -> [Data | Next] -> NULL
    ```

2.  **双向链表 (Doubly Linked List)**:
    *   每个节点有两个指针:一个指向下一个节点 (next),一个指向上一个节点 (previous)。
    *   可以双向遍历,既可以从头节点向尾节点,也可以从尾节点向头节点。
    *   更灵活,但需要额外的内存存储前驱指针。

    ```
    NULL <- [Prev | Data | Next] <-> [Prev | Data | Next] <-> [Prev | Data | Next] -> NULL
    ```

3.  **循环链表 (Circular Linked List)**:
    *   单链表或双向链表的变体。
    *   最后一个节点的指针指向头节点,形成一个环。
    *   可以从任何节点开始遍历整个链表。

    ```
    单向循环链表:  Head -> [Data | Next] -> [Data | Next] -> [Data | Next] -> Head
    双向循环链表:  [Prev | Data | Next] <-> [Prev | Data | Next] <-> [Prev | Data | Next] <->  ...
    ```

**链表的基本操作**

链表支持多种基本操作,这些操作是构建和管理链表的基础:

1.  **创建链表 (Create)**:创建一个链表的初始空状态 (通常需要一个头节点)。

2.  **插入节点 (Insert)**:
    *   **在链表头部插入 (Insert at the beginning)**:创建一个新节点,使其 `next` 指向原头节点,然后更新头节点指向这个新节点。  时间复杂度为 O(1)。
    *   **在链表尾部插入 (Insert at the end)**:遍历链表找到最后一个节点,使其 `next` 指向新节点。  时间复杂度为 O(n) (需要遍历)。
    *   **在指定位置插入 (Insert at a specific position)**:遍历链表找到指定位置的前一个节点,修改其 `next` 指针,使之指向新节点;新节点的 `next` 指针指向原位置的节点。 时间复杂度为 O(n) (需要遍历)。

3.  **删除节点 (Delete)**:
    *   **删除头节点 (Delete at the beginning)**:更新头节点指向第二个节点。  时间复杂度为 O(1)。
    *   **删除尾节点 (Delete at the end)**:找到倒数第二个节点,将其 `next` 指针设置为 `NULL`。  时间复杂度为 O(n) (需要遍历)。
    *   **删除指定节点 (Delete a specific node)**:遍历链表找到要删除节点的前一个节点,修改其 `next` 指针,跳过要删除的节点。  时间复杂度为 O(n) (需要遍历)。

4.  **查找节点 (Search)**:
    *   遍历链表,逐个比较节点的数据,找到目标节点。  时间复杂度为 O(n)。

5.  **更新节点 (Update)**:
    *   找到要更新的节点(通常通过查找),修改其数据。  时间复杂度为 O(n) (需要查找)。

6.  **遍历链表 (Traversal)**:
    *   从头节点开始,沿着 `next` (或者 `next` 和 `prev`) 指针,依次访问每个节点。

**链表的优缺点**

**优点:**

*   **动态大小 (Dynamic Size)**:链表的长度可以动态地增加或减少,不需要预先分配固定大小的存储空间。
*   **插入和删除操作高效 (Efficient Insertion and Deletion)**:在链表的中间插入或删除节点通常比数组更快 (不需要移动大量元素),  尤其是在已知要插入/删除位置的情况下。
*   **内存分配灵活 (Flexible Memory Allocation)**:节点可以分散在内存的任何位置,无需像数组那样需要连续的内存空间。

**缺点:**

*   **访问速度慢 (Slow Access)**:随机访问元素效率低,必须从头节点开始遍历链表,才能找到特定位置的节点。  数组的随机访问速度是O(1),而链表是O(n)。
*   **额外的内存开销 (Extra Memory Overhead)**:每个节点需要额外的内存空间来存储 next (和 prev) 指针。
*   **缓存不友好 (Cache Inefficient)**:链表的节点在内存中可能不连续,这使得 CPU 缓存的利用率降低,影响性能。
*   **调试比较困难 (Harder to Debug)**:指针操作容易出错,导致内存泄漏和程序崩溃。

**链表的应用场景**

链表在很多情况下都适用,尤其是在需要频繁进行插入和删除操作的场景:

*   **实现动态内存管理**: 在需要根据需求动态生成或删除内存块的场景中,链表非常有用。
*   **表示稀疏矩阵 (Sparse Matrices)**: 链表可以用来存储稀疏矩阵中的非零元素,节省存储空间。
*   **构建栈 (Stack) 和队列 (Queue)**: 链表是常用的数据结构,用于实现栈和队列,因为它们支持高效的插入和删除操作。
*   **多项式表示**: 用链表来表示多项式,每个节点代表一个项。
*   **图形的邻接表**:  表示图的邻接表,每个节点代表一个顶点,其链表存储与该顶点相邻的顶点。
*   **文件系统**:链表用于管理文件系统中的文件和目录的链接。
*   **音乐播放器**: 在音乐播放器中,链表可以用来管理播放列表。

**代码示例 (C++  单链表)**

```cpp
#include <iostream>

// 定义链表节点
struct Node {
    int data;        // 存储的数据
    Node* next;      // 指向下一个节点的指针

    // 构造函数
    Node(int value) : data(value), next(nullptr) {}
};

// 链表类
class LinkedList {
public:
    Node* head;   // 头节点

    // 构造函数
    LinkedList() : head(nullptr) {} // 初始化为空链表

    //  添加节点到链表尾部
    void append(int data) {
        Node* newNode = new Node(data);  // 创建新节点

        if (head == nullptr) {
            // 链表为空,新节点成为头节点
            head = newNode;
            return;
        }

        Node* current = head;  // 从头节点开始遍历
        while (current->next != nullptr) {
            current = current->next;  // 移动到下一个节点
        }

        current->next = newNode; // 将新节点添加到链表尾部
    }

    //  打印链表
    void printList() {
        Node* current = head;
        while (current != nullptr) {
            std::cout << current->data << " -> ";
            current = current->next;
        }
        std::cout << "NULL" << std::endl;
    }

    //  在指定位置插入节点  (index 从 0 开始)
    void insertAt(int index, int data) {
        if (index < 0) {
            std::cout << "Invalid index." << std::endl;
            return;
        }

        Node* newNode = new Node(data);

        if (index == 0) {  // 插入到头部
            newNode->next = head;
            head = newNode;
            return;
        }

        Node* current = head;
        int i = 0;
        while (current != nullptr && i < index - 1) {
            current = current->next;
            i++;
        }

        if (current == nullptr) {  // 索引超出范围
             std::cout << "Index out of bounds." << std::endl;
             delete newNode; // 避免内存泄漏
            return;
        }

        newNode->next = current->next;
        current->next = newNode;
    }

    // 删除指定索引的节点 (index 从 0 开始)
    void deleteAt(int index) {
        if (index < 0 || head == nullptr) {
            std::cout << "Invalid index or empty list." << std::endl;
            return;
        }

        if (index == 0) { // 删除头节点
            Node* temp = head;
            head = head->next;
            delete temp;
            return;
        }

        Node* current = head;
        Node* prev = nullptr;
        int i = 0;
        while (current != nullptr && i < index) {
            prev = current;
            current = current->next;
            i++;
        }

        if (current == nullptr) {  // 索引超出范围
            std::cout << "Index out of bounds." << std::endl;
            return;
        }

        prev->next = current->next;
        delete current;
    }


    // 析构函数,释放链表的所有节点,避免内存泄漏.
    ~LinkedList() {
        Node* current = head;
        while (current != nullptr) {
            Node* next = current->next;
            delete current;
            current = next;
        }
        head = nullptr;  // 确保 head 为空
    }
};


int main() {
    LinkedList myList;

    myList.append(10);
    myList.append(20);
    myList.append(30);

    std::cout << "链表的内容: ";
    myList.printList();   // 输出: 10 -> 20 -> 30 -> NULL

    myList.insertAt(1, 15);  // 在索引 1 处插入 15
    std::cout << "插入后链表的内容: ";
    myList.printList();   // 输出: 10 -> 15 -> 20 -> 30 -> NULL

    myList.deleteAt(2);   // 删除索引为 2 的节点
    std::cout << "删除后链表的内容: ";
    myList.printList();   // 输出: 10 -> 15 -> 30 -> NULL

    return 0;
}
```

**代码说明:**

1.  **`Node` 结构体:**  定义链表的节点,包含数据 `data`  和指向下一个节点的指针 `next`。
2.  **`LinkedList` 类:** 封装了链表的操作。
3.  **`append(int data)` (在尾部添加):**  遍历链表找到最后一个节点,将新节点添加到链表的尾部。
4.  **`printList()` (打印):** 遍历链表并打印节点的数据。
5.  **`insertAt(int index, int data)` (在特定位置插入):**  根据索引找到插入位置的前一个节点,修改指针实现插入。
6.  **`deleteAt(int index)` (删除):**  根据索引找到要删除的节点的前一个节点,修改指针实现删除。  注意要考虑删除头节点的情况。
7.  **`~LinkedList()` (析构函数):** 释放所有节点的内存,防止内存泄漏。  析构函数在对象生命周期结束时自动调用。

**总结**

链表是一种重要的数据结构,它在许多应用中都非常有用。 了解链表的概念、不同类型、基本操作以及优缺点,可以帮助你更好地理解和使用它。  希望这个详细的介绍对你有帮助!  如果你有任何进一步的问题,请随时提问。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值