【问题标题】:How do I remove duplicate strings from an array in C?如何从 C 中的数组中删除重复的字符串?
【发布时间】:2011-03-23 19:06:01
【问题描述】:

我有一个 C 中的字符串数组和一个表示数组中有多少个字符串的整数。

char *strarray[MAX];  
int strcount;

在此数组中,最高索引(其中 10 高于 0)是最近添加的项目,最低索引是最远添加的项目。 数组中项目的顺序很重要。

我需要一种快速的方法来检查数组中的重复项,删除除最高索引重复项之外的所有重复项,然后折叠数组。

例如:

strarray[0] = "Line 1"; 
strarray[1] = "Line 2"; 
strarray[2] = "Line 3"; 
strarray[3] = "Line 2"; 
strarray[4] = "Line 4";

会变成:

strarray[0] = "Line 1"; 
strarray[1] = "Line 3"; 
strarray[2] = "Line 2"; 
strarray[3] = "Line 4";

原始数组的索引 1 被删除,索引 2、3 和 4 向下滑动以填补空白。

我对如何做到这一点有一个想法。它未经测试,我目前正在尝试对其进行编码,但根据我的模糊理解,我确信这是一个可怕的算法。

每次将新字符串添加到 strarray 时,都会运行下面介绍的算法。

为了表明我正在尝试,我将在下面包含我提出的算法:

  1. 搜索整个 strarray 以匹配 str
  2. 如果不匹配,什么也不做
  3. 如果找到匹配项,将 str 放入 strarray
  4. 现在我们有一个最多包含 1 个重复条目的 strarray
  5. 将最高索引的 strarray 字符串添加到临时字符串数组的最低索引
  6. 继续向下进入 strarray 并检查每个元素
  7. 如果发现重复,跳过它
  8. 如果不是,则将其添加到临时字符串数组的下一个最高索引
  9. 反转临时字符串数组并复制到 strarray

再一次,这是未经测试的(我现在正在实施它)。我只是希望有人会有更好的解决方案。

项目的顺序很重要,代码必须使用 C 语言(不是 C++)。应删除最低索引重复项并保留单个最高索引。

谢谢!

【问题讨论】:

    标签: c algorithm arrays duplicates duplicate-removal


    【解决方案1】:

    典型的高效唯一函数是:

    1. 对给定的数组进行排序。
    2. 验证是否已设置连续次运行同一项目,以便只剩下一个。

    我相信你可以使用qsort 结合strcmp 来完成第一部分;不过,写一个高效的remove 就交给你了。

    很遗憾,我在这里没有具体的想法;这对我来说是一个灰色地带,因为我通常使用 C++,这很简单:

    std::vector<std::string> src;
    std::sort(src.begin(), src.end());
    src.remove(std::unique(src.begin(), src.end()), src.end);
    

    我知道你不能使用 C++,但实现应该基本相同。

    因为你需要保存原始订单,你可以有这样的东西:

    typedef struct
    {
        int originalPosition;
        char * string;
    } tempUniqueEntry;
    

    string 进行第一次排序,删除排序集中的唯一元素集,然后对originalPosition 进行处理。这样您仍然可以获得 O(n lg n) 性能,但不会丢失原始顺序。

    编辑2: std::unique的简单C实现示例:

    tempUniqueEntry* unique ( tempUniqueEntry * first, tempUniqueEntry * last )
    {
      tempUniqueEntry *result=first;
      while (++first != last)
      {
        if (strcmp(result->string,first->string))
          *(++result)=*first;
      }
      return ++result;
    }
    

    【讨论】:

    • 排序不会丢失元素的顺序吗?
    • 感谢您的编辑!我对分类有点生疏,但我可以从这里拿走。我要试试你的想法,看看效果如何。据我了解,我需要迭代 strarray,在此过程中制作 tempUniqueEntry 的临时数组。按字符串对 tempArray 进行排序,删除重复项,按位置再次对 tempArray 进行排序,然后重建 strarray。对吗?
    • @Jerry:是的,没错。您不需要实现自己的排序算法;标准库的qsort 可以做到这一点。您只需要定义比较函数。至于删除,您可能希望关注std::unique,(例如:cplusplus.com/reference/algorithm/unique),因为它可以使整个数组在线性时间内唯一——删除时不需要不断调整数组的大小。 (只需自己重新实现std::unique,用返回equals的函数指针替换谓词函数,用指针替换迭代器)
    【解决方案2】:

    您能否在输入进入数组时对其进行控制?如果是这样,请执行以下操作:

    int addToArray(const char * toadd, char * strarray[], int strcount)
    {
        const int toaddlen = strlen(toadd);
    
        // Add new string to end.
        // Remember to add one for the \0 terminator.
        strarray[strcount] = malloc(sizeof(char) * (toaddlen + 1));
        strncpy(strarray[strcount], toadd, toaddlen + 1);
    
        // Search for a duplicate.
        // Note that we are cutting the new array short by one.
        for(int i = 0; i < strcount; ++i)
        {
            if (strncmp(strarray[i], toaddlen + 1) == 0)
            {
                // Found duplicate.
                // Remove it and compact.
                // Note use of new array size here.  
                free(strarray[i]);
                for(int k = i + 1; k < strcount + 1; ++k)
                    strarray[i] = strarray[k];
    
                strarray[strcount] = null;
                return strcount;
            }
        }
    
        // No duplicate found.
        return (strcount + 1);
    }
    

    您始终可以使用上述函数循环现有数组的元素,构建一个没有重复的新数组。

    PS:如果你经常做这种类型的操作,你应该远离数组作为你的存储结构,而改用链表。它们对于从末端以外的位置移除元素的效率要高得多。

    【讨论】:

    • 这很好用;它比 OP 的原始解决方案更好。 +1。但不幸的是,性能仍然是 n-squared :(
    • 据我了解您的解决方案,如果它已经在 strarray 中,它什么也不做。如果不是,它会添加它。如果我的理解是正确的,这是行不通的。我可以在输入数组时控制输入,但这种方法不会产生我在帖子中给出的结果。我需要幸存的副本位于最高而不是最低的索引中。如果 toadd 已存在于 strarray[1] 中,则不会将其添加到 strarray[N] 其中 N > 1
    • @Jerry Smith 你的例子是错误的。它应该是 1, 3, 2, 4。我会尽快更正我的解决方案……但这是一个更昂贵的操作,因为它每次都需要压缩数组。
    • @Jerry Smith 添加了删除和压缩。最后请注意我的PS。
    • @Jerry Smith 感谢您理解在时间紧迫的情况下键入 Web 表单的某些代码如果不进行细微修改可能无法编译,而是理解所提出的想法是重要的部分。
    【解决方案3】:

    我不太了解您提出的算法(我不明白在第 5 步中将字符串添加到索引中意味着什么),但我会做的是:

    unsigned int i;
    for (i = n; i > 0; i--)
    {
        unsigned int j;
    
        if (strarray[i - 1] == NULL)
        {
            continue;
        }
    
        for (j = i - 1; j > 0; j--)
        {
            if (strcmp(strarray[i - 1], strarray[j - 1]) == 0)
            {
                strarray[j - 1] = NULL;
            }
        }
    }
    

    然后你只需要从你的数组中过滤掉空指针(我将把它留作练习)。

    另一种方法是在数组上进行反向迭代,并在执行过程中将每个项目插入(平衡)二叉搜索树中。如果该项已经在二叉搜索树中,则标记数组项(例如将数组元素设置为NULL)并继续。处理完整个数组后,像以前一样过滤掉标记的元素。这会产生更多的开销并消耗更多的空间,但它的运行时间将是 O(n log n) 而不是 O(n^2)。

    【讨论】:

    • 我在第 5 步中的意思很简单: // 其中 0 是最低索引,9 是可用的最大索引 temparray[0] = strarray[9];
    【解决方案4】:

    使用qsort之类的算法对数组进行排序(在终端中使用man 3 qsort,看看它应该如何使用),然后使用函数strcmp比较字符串并查找重复项

    如果您想保持原始顺序,您可以使用嵌套两个 for 的 O(N^2) 复杂度算法,第一个每次选择一个元素进行比较,第二个 for 将用于扫描数组的其余部分来查找所选元素是否重复。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-28
      • 1970-01-01
      • 2015-06-07
      • 2011-08-08
      相关资源
      最近更新 更多