跳转至

AB13-删除单链表中第一个值为x的结点

代码实现

C
/**
 * 删除单链表中第一个值为x的结点
 * @param L 指向链表头结点的指针
 * @param x 要删除的目标值
 * @return 删除成功返回true,未找到返回false
 */
bool del_x(LinkList L, int x) {
    // 边界条件检查:链表是否存在
    if (L == NULL) {
        return false;
    }

    // r指向当前结点,用于查找目标结点的前驱
    LNode* r = L;

    // 遍历链表查找目标结点
    while (r->next != NULL) {
        // 如果找到值为x的目标结点(r的后继结点)
        if (r->next->data == x) {
            // 执行删除操作
            LNode* p = r->next;      // p指向要删除的结点
            r->next = p->next;         // 跳过要删除的结点
            free(p);                   // 释放被删除结点的内存
            return true;               // 删除成功,返回true
        }
        r = r->next;  // 继续向后查找
    }

    // 遍历完整个链表都未找到目标结点,删除失败
    return false;
}

算法执行流程图解

删除过程示例:

Text Only
原链表:L→[ ]→[3]→[7]→[2]→[9]→[2]→[5]→NULL  删除x=2

查找过程:
r=L: r->next=[3], 3==2? 否 → r=[3]
r=[3]: r->next=[7], 7==2? 否 → r=[7]
r=[7]: r->next=[2], 2==2? 是 → 找到目标结点!

删除操作:
r=[7]  p=[2]  r->next=[9]

1. p = r->next;        → p指向要删除的[2]
2. r->next = p->next;  → [7]→[9]  (跳过[2])
3. free(p);            → 释放[2]的内存

删除后:L→[ ]→[3]→[7]→[9]→[2]→[5]→NULL

删除操作详细图解

Text Only
删除前:L→[ ]→[A]→[B]→[C]→[D]→NULL
               ↑     ↑     ↑
               r    p(要删除) 

删除步骤:
步骤1:p = r->next
       L→[ ]→[A]→[B]→[C]→[D]→NULL
                    p

步骤2:r->next = p->next  
       L→[ ]→[A]→[C]→[D]→NULL
               ↑     ↑
               r    原来的[C]

       [B]→[C]→[D]→NULL  (原来的B结点被孤立)
            p

步骤3:free(p)
       释放B结点的内存空间

复杂度分析

时间复杂度:O(n)

  • 最好情况:O(1) - 目标结点在第一个位置
  • 最坏情况:O(n) - 目标结点在最后一个位置或不存在
  • 平均情况:O(n) - 平均需要查找n/2个结点
  • 总体时间复杂度O(n)

空间复杂度:O(1)

  • 额外空间:只使用了常数个指针变量:rp
  • 算法额外空间复杂度O(1)

算法变体:删除所有值为x的结点

C
/**
 * 删除单链表中所有值为x的结点
 * @param L 指向链表头结点的指针
 * @param x 要删除的目标值
 * @return 删除的结点个数
 */
int del_All(LinkList L, int x) {
    if (L == NULL) return 0;

    int count = 0;     // 记录删除的结点个数
    LinkList r = L;    // 前驱指针

    while (r->next != NULL) {
        if (r->next->data == x) {
            // 删除结点
            LinkList p = r->next;
            r->next = p->next;
            free(p);
            count++;
            // 注意:不移动r指针,因为可能连续有相同值的结点
        } else {
            r = r->next;  // 只有在不删除时才移动指针
        }
    }

    return count;
}

递归版本实现

C
/**
 * 递归版本:删除第一个值为x的结点
 */
bool del_Recursive(LinkList L, int x) {
    if (L == NULL || L->next == NULL) {
        return false;
    }

    if (L->next->data == x) {
        // 找到目标结点,执行删除
        LinkList p = L->next;
        L->next = p->next;
        free(p);
        return true;
    }

    // 递归处理后续结点
    return del_Recursive(L->next, x);
}

算法特点

核心思想: 使用前驱指针遍历链表,当发现目标结点时,通过修改前驱结点的指针域来跳过目标结点,从而实现删除。

关键技巧: 1. 前驱指针法:使用r->next来访问当前结点,r作为前驱 2. 安全删除:先保存要删除的结点指针,再修改链接关系,最后释放内存

重要注意点: - 必须通过前驱结点来删除目标结点 - 删除后要及时释放内存,避免内存泄漏 - 正确处理未找到目标结点的情况

与顺序表删除的对比

特性 顺序表 链表
查找时间 O(n) O(n)
删除时间 O(n) O(1)
空间复杂度 O(1) O(1)
内存管理 无需释放 需要手动释放

链表的优势在于删除操作本身是O(1)的,但查找仍需O(n)时间。