2. 单链表的插入、删除与反转

单链表的插入、删除与反转 学习卡片

单链表操作的核心:指针重连与边界处理

单链表由一组离散节点组成,每个节点包含数据域和指向后继的指针域。头插、尾插、按值删除和原地反转这四类操作,本质上都是对 next 指针的重新连接。理解指针的“先保存、后改向”顺序,是避免指针丢失的关键。

头插法:新节点成为新头 步骤:new.next = head; head = new new head A NULL 尾插法:遍历到尾部再挂接 步骤:tail.next = new; new.next = NULL head A B new 按值删除:prev 与 curr 双指针 步骤:prev.next = curr.next; free(curr) head prev curr next prev.next 跳过 curr 原地反转:三指针迭代 步骤:next = curr.next; curr.next = prev; prev = curr; curr = next prev curr next 每轮循环后整体右移 边界条件检查清单 空链表 head == NULL 插入直接返回新节点 删除头节点 需更新 head 指针 head = head.next 删除尾节点 prev.next = NULL 注意释放内存 反转空/单节点 直接返回 head 无需进入循环

节点定义与头插、尾插实现

先定义链表节点结构,再分别实现头插和尾插。头插的时间复杂度为 O(1),尾插若维护尾指针也为 O(1),否则需要 O(n) 遍历。

#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node *next;
} Node;

/* 创建新节点 */
Node* createNode(int value) {
    Node *node = (Node*)malloc(sizeof(Node));
    if (!node) return NULL;
    node->data = value;
    node->next = NULL;
    return node;
}

/* 头插:新节点成为新的头节点 */
Node* insertHead(Node *head, int value) {
    Node *newNode = createNode(value);
    if (!newNode) return head;
    newNode->next = head;   /* 先让新节点指向原头 */
    return newNode;          /* 再更新头指针 */
}

/* 尾插:遍历到尾部后挂接 */
Node* insertTail(Node *head, int value) {
    Node *newNode = createNode(value);
    if (!newNode) return head;
    if (head == NULL) return newNode;  /* 空链表边界 */
    Node *cur = head;
    while (cur->next != NULL) {
        cur = cur->next;
    }
    cur->next = newNode;
    return head;
}
头插时务必先执行 newNode->next = head,再执行 head = newNode。若顺序颠倒,原链表头节点地址丢失,造成内存泄漏。

按值删除与原地反转

按值删除需要同时维护前驱指针 prev 和当前指针 curr。原地反转则通过三个指针 prev、curr、next 逐步将每个节点的 next 反向。

/* 按值删除第一个匹配节点 */
Node* deleteByValue(Node *head, int value) {
    if (head == NULL) return NULL;  /* 空链表边界 */

    /* 删除头节点的情况 */
    if (head->data == value) {
        Node *temp = head;
        head = head->next;
        free(temp);
        return head;
    }

    Node *prev = head;
    Node *curr = head->next;
    while (curr != NULL) {
        if (curr->data == value) {
            prev->next = curr->next;  /* 跳过 curr */
            free(curr);
            return head;
        }
        prev = curr;
        curr = curr->next;
    }
    return head;  /* 未找到,原样返回 */
}

/* 原地反转:三指针迭代 */
Node* reverseList(Node *head) {
    Node *prev = NULL;
    Node *curr = head;
    while (curr != NULL) {
        Node *next = curr->next;  /* 先保存后继 */
        curr->next = prev;         /* 反转指针方向 */
        prev = curr;               /* prev 前移 */
        curr = next;               /* curr 前移 */
    }
    return prev;  /* prev 最终指向新头 */
}
反转循环中必须先保存 curr->next,再修改 curr->next = prev。若先改向再取后继,curr->next 已指向 prev,后续节点全部丢失。

完整测试与验证

下面用一个 main 函数串联四类操作,并打印每一步的链表状态,便于观察指针变化和边界处理是否正确。

/* 打印链表 */
void printList(Node *head) {
    Node *cur = head;
    while (cur != NULL) {
        printf("%d -> ", cur->data);
        cur = cur->next;
    }
    printf("NULL\n");
}

/* 释放整条链表 */
void freeList(Node *head) {
    while (head != NULL) {
        Node *temp = head;
        head = head->next;
        free(temp);
    }
}

int main(void) {
    Node *head = NULL;

    /* 尾插构建 1 -> 2 -> 3 */
    head = insertTail(head, 1);
    head = insertTail(head, 2);
    head = insertTail(head, 3);
    printf("尾插后: ");
    printList(head);  /* 1 -> 2 -> 3 -> NULL */

    /* 头插 0 */
    head = insertHead(head, 0);
    printf("头插后: ");
    printList(head);  /* 0 -> 1 -> 2 -> 3 -> NULL */

    /* 删除值 2 */
    head = deleteByValue(head, 2);
    printf("删除2后: ");
    printList(head);  /* 0 -> 1 -> 3 -> NULL */

    /* 删除头节点 0 */
    head = deleteByValue(head, 0);
    printf("删除0后: ");
    printList(head);  /* 1 -> 3 -> NULL */

    /* 原地反转 */
    head = reverseList(head);
    printf("反转后: ");
    printList(head);  /* 3 -> 1 -> NULL */

    /* 边界:空链表反转 */
    head = reverseList(NULL);
    printf("空链表反转: ");
    printList(head);  /* NULL */

    freeList(head);
    return 0;
}

关键验证点

操作 预期结果 边界条件
头插 0 0 -> 1 -> 2 -> 3 -> NULL 空链表时直接返回新节点
尾插 1,2,3 1 -> 2 -> 3 -> NULL 空链表时新节点即头节点
删除 2 0 -> 1 -> 3 -> NULL 删除头节点需更新 head
删除 0 1 -> 3 -> NULL 头节点匹配时直接跳过
反转 3 -> 1 -> NULL 空链表或单节点直接返回
验证口诀:头插先连后改头,尾插判空再遍历;删除双指针跳节点,反转三指针防丢失。每步打印链表状态,边界用例单独跑。