跳转至

AB14-删除带头结点单链表中所有值为x的结点

代码实现

C
/**
 * 删除带头结点单链表中所有值为x的结点
 * @param L 指向链表头结点的指针
 * @param x 要删除的目标值
 * @return 有结点被删除返回true,无结点被删除返回false
 */
bool Del(LinkList L, int x) {
    // r指向当前结点的前驱结点(从头结点开始)
    LinkList r = L;

    // 标记是否有结点被删除
    int flag = 0;

    // 遍历链表,查找并删除所有值为x的结点
    while (r->next != NULL) {
        // 如果下一个结点的值等于x
        if (r->next->data == x) {
            flag = 1;  // 标记有结点被删除

            // 准备删除操作
            LinkList p = r->next;  // p指向要删除的结点

            // 删除操作:绕过要删除的结点
            r->next = p->next;     // 前驱结点指向被删除结点的后继

            // 释放被删除结点的内存
            free(p);

            // 注意:这里不移动r指针,因为r->next已经指向了新的结点
            // 新的r->next可能仍然是x,需要继续检查
        } else {
            // 如果下一个结点不是要删除的结点,则移动r指针
            r = r->next;
        }
    }

    // 根据flag判断是否有结点被删除
    if (flag == 1) {
        return true;   // 有结点被删除
    } else {
        return false;  // 无结点被删除
    }
}

算法执行流程图解

删除过程示例:

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

执行过程:
r=L: r->next=[3], 3==2? 否 → r=[3]
r=[3]: r->next=[2], 2==2? 是 → 删除[2]
       r->next=[7], 7==2? 否 → r=[7] 
r=[7]: r->next=[2], 2==2? 是 → 删除[2]
       r->next=[9], 9==2? 否 → r=[9]
r=[9]: r->next=[2], 2==2? 是 → 删除[2]
       r->next=[5], 5==2? 否 → r=[5]
r=[5]: r->next=NULL, 结束

结果:L→[ ]→[3]→[7]→[9]→[5]→NULL

关键操作图解

连续删除的情况:

Text Only
删除前:L→[ ]→[A]→[X]→[X]→[B]→NULL
              ↑    ↑    ↑
              r   r->next

第一次删除:
1. p = r->next = [X]
2. r->next = p->next = [X]  // r→[A]→[X]→[B]→NULL
3. free(p) = [X]

此时:L→[ ]→[A]→[X]→[B]→NULL
             ↑    ↑
             r   r->next

注意:r没有移动!继续检查新的r->next

复杂度分析

时间复杂度:O(n)

  • 遍历时间:需要遍历整个链表一次,O(n)
  • 删除时间:每次删除操作O(1),最多删除n个结点,总计O(n)
  • 总体时间复杂度O(n)

空间复杂度:O(1)

  • 额外空间:只使用了常数个指针变量:rpflag
  • 内存释放:释放被删除结点的内存,不属于额外空间
  • 算法额外空间复杂度O(1)

算法优化版本

优化版本1:使用布尔值替代整数标记

C
/**
 * 优化版本1:使用布尔值替代整数标记
 */
bool Del_Optimized1(LinkList L, int x) {
    if (L == NULL) return false;

    LinkList r = L;
    bool deleted = false;  // 使用布尔值更清晰

    while (r->next != NULL) {
        if (r->next->data == x) {
            deleted = true;
            LinkList p = r->next;
            r->next = p->next;
            free(p);
            // 不移动r,继续检查新的r->next
        } else {
            r = r->next;
        }
    }

    return deleted;
}

优化版本2:返回删除的结点个数

C
/**
 * 优化版本2:返回删除的结点个数
 */
int Del_And_Count(LinkList L, int x) {
    if (L == NULL) return 0;

    LinkList r = L;
    int count = 0;

    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;
}

与单个删除的对比

特征 删除第一个 删除所有
指针移动 找到后立即返回,移动r 找到后不移动r,继续检查
实现复杂度 相对简单 需要特别注意指针移动逻辑
时间复杂度 O(n) O(n)
返回值 成功/失败 成功/失败

算法关键点总结

核心思想: - 遍历链表,删除所有值为x的结点 - 使用前驱指针技术,便于删除操作 - 关键是理解删除后不移动前驱指针的原因

重要细节: 1. 指针移动策略:删除时不移动r,不删除时才移动r 2. 内存管理:及时释放被删除结点的内存 3. 边界处理:正确处理空链表和连续相同值的情况

易错点: - 删除后忘记移动指针导致遗漏连续的相同值 - 删除后错误地移动指针导致逻辑错误 - 内存泄漏问题

这种算法在实际应用中很常见,比如清理链表中的特定数据或过滤操作。