跳转至

AB38-删除顺序表中所有值为x的元素

建议优化版本

代码实现

C
/**
 * 删除顺序表中所有值为x的元素
 * @param L 指向顺序表的指针
 * @param x 要删除的目标值
 * @return 有元素被删除返回true,无元素被删除返回false
 */
bool del(Sqlist *L, int x) {
    // k指向新顺序表的当前位置(重构后顺序表的下标)
    int k = 0;

    // 遍历原顺序表,重构新的顺序表
    for (int i = 0; i < L->length; i++) {
        // 如果当前元素不是要删除的元素
        if (L->data[i] != x) {
            // 将该元素复制到新顺序表的位置
            L->data[k] = L->data[i];
            k++;  // 新顺序表位置后移
        }
        // 如果当前元素是要删除的元素,则跳过不复制
    }

    // 判断是否有元素被删除
    if (L->length == k) {
        // 长度未变,说明没有元素被删除
        return false;
    } else {
        // 长度改变,说明有元素被删除
        L->length = k;  // 更新顺序表长度
        return true;
    }
}

算法执行流程图解

删除过程示例:

Text Only
原顺序表:[3][2][7][2][9][2][5]  (length=7)  删除x=2

执行过程:
i=0: data[0]=3 ≠ 2 → data[0]=3, k=1
i=1: data[1]=2 = 2 → 跳过
i=2: data[2]=7 ≠ 2 → data[1]=7, k=2  
i=3: data[3]=2 = 2 → 跳过
i=4: data[4]=9 ≠ 2 → data[2]=9, k=3
i=5: data[5]=2 = 2 → 跳过
i=6: data[6]=5 ≠ 2 → data[3]=5, k=4

结果分析:
原长度:7
新长度:4
有元素被删除

最终结果:[3][7][9][5][9][2][5]  (length=4)
           ↑     ↑
        有效数据  无效数据(但不影响使用)

算法重构过程可视化

Text Only
原顺序表:[3][2][7][2][9][2][5]
          0  1  2  3  4  5  6

重构过程:
k=0: 检查[3]≠2 → [3][2][7][2][9][2][5]  k=1
k=1: 检查[2]=2 →  [3][2][7][2][9][2][5]  k=1 (跳过)
k=1: 检查[7]≠2 → [3][7][7][2][9][2][5]  k=2
k=2: 检查[2]=2 →  [3][7][7][2][9][2][5]  k=2 (跳过)
k=2: 检查[9]≠2 → [3][7][9][2][9][2][5]  k=3
k=3: 检查[2]=2 →  [3][7][9][2][9][2][5]  k=3 (跳过)
k=3: 检查[5]≠2 → [3][7][9][5][9][2][5]  k=4

最终有效部分:[3][7][9][5]  (length=4)

复杂度分析

时间复杂度:O(n)

  • 遍历时间:需要遍历整个顺序表一次,O(n)
  • 复制时间:最多复制n个元素,O(n)
  • 总体时间复杂度O(n)

空间复杂度:O(1)

  • 只使用了常数个额外变量:ki
  • 原地重构,无需额外存储空间
  • 空间复杂度:O(1)

优化版本:逻辑更清晰,添加边界检查

C
/**
 * 优化版本:逻辑更清晰,添加边界检查
 */
bool del_Optimized(Sqlist *L, int x) {
    // 边界条件检查
    if (L == NULL || L->length <= 0) {
        return false;
    }

    int k = 0;  // 新顺序表的下标

    // 重构顺序表:保留非x元素
    for (int i = 0; i < L->length; i++) {
        if (L->data[i] != x) {
            if (k != i) {  // 避免不必要的自复制
                L->data[k++] = L->data[i];
            }
        }
    }

    // 判断是否有元素被删除并更新长度
    if (L->length != k) {
        L->length = k;
        return true;   // 有元素被删除
    }
    return false;      // 无元素被删除
}

与单个删除算法的对比

特征 删除第一个 删除所有
时间复杂度 O(n) O(n)
实现复杂度 较复杂(需要前移操作) 较简单(重构方式)
空间利用 原地前移 原地重构
适用场景 只需删除一个 需要删除全部

算法变体:返回删除元素个数

C
/**
 * 删除所有值为x的元素,并返回删除的个数
 * @param L 指向顺序表的指针
 * @param x 要删除的目标值
 * @return 删除的元素个数
 */
int del_and_count(Sqlist *L, int x) {
    int old_length = L->length;  // 记录原长度
    int k = 0;                   // 新顺序表下标

    // 重构顺序表
    for (int i = 0; i < L->length; i++) {
        if (L->data[i] != x) {
            L->data[k] = L->data[i];
            k++;
        }
    }

    L->length = k;  // 更新长度
    return old_length - k;  // 返回删除的元素个数
}

算法特点

核心思想: 不进行元素前移操作,而是通过重构的方式,将不需要删除的元素依次复制到顺序表前面,形成新的顺序表。

优点: - 实现简单,逻辑清晰 - 只需一次遍历 - 避免了多次元素移动的开销 - 原地操作,空间效率高

关键技巧: 使用双下标:i遍历原表,k构建新表

注意点: - 正确更新顺序表长度 - 准确判断是否有元素被删除 - 理解重构后数组后部数据虽然存在但已无效