有效地连接左侧的 N 个字符数组,C

Posted

技术标签:

【中文标题】有效地连接左侧的 N 个字符数组,C【英文标题】:Concatenate N char arrays on left hand side efficiently, C 【发布时间】:2018-07-08 23:11:34 【问题描述】:

假设我有 N 个要连接在一起的 char 数组。每个 char 数组都存储在一个单独的结构中。为了访问 stuct1 中的 char 数组,我需要访问 struct2,要访问 struct2 我需要访问 struct3,等等(想象一个单链表,头部在 structN,尾部在 struct1)。

我想连接每个结构中的每个 char 数组,以便 struct1 中的 char 数组首先出现,而 structN 中的 char 数组最后出现。

例如,假设我与 struct1、struct2 和 struct3 关联的 char 数组的内容为“A”、“B”、“C”。我想得到结果字符数组“ABC”。但是,如上所述,要访问 structX,我必须先访问 structX+1。因此,在左侧连接这些 char 数组会更有效率;我不必继续遍历所有结构。

有没有办法在 C 中有效地做到这一点(即 strcat、snprintf 等),还是我必须手动操作每个 char 数组以获得我想要的(或遍历列表,保存指向结构的指针,以及回去工作)?

编辑(清晰) 假设我有一个链接的链表。每个元素都有一个 char 数组。我想以相反的顺序连接字符数组。有没有办法在不通过列表两次的情况下做到这一点?我知道运行时所有 char 数组的最大大小,但在访问列表的每个元素之前我不知道它们各自的大小(当我访问元素 X 时,我知道存储在 X 中的 char 数组的大小)

【问题讨论】:

N的最大值是多少? 为什么不编写自己的函数来将一个字符串附加到另一个字符串? 确实有可能,但是您必须确保复制到的缓冲区足够大以适应这些对象的内容,或者在每次迭代时使用realloc,这可能会很慢。使用 O(2N)(仅分配一次内存时)和 O(N) 方法进行一些基准测试,然后您将看到更好的选择。 在问题中显示你的结构。说明你尝试了什么。 但是您还没有说或显示其他字段之一是否是 char 数组中字符串的长度(或非字符串的长度 - 没有空终止符 - 数组中的数据)。但是,为了我的钱,您需要两次通过列表,但与您可能需要做的复制相比,这是一个微不足道的成本。如果你不记录结构中数组的长度,你就增加了很多操作成本。您必须知道它们在创建时有多长;不要丢弃有价值的信息。别忘了你可以realloc() 来缩小数组。 【参考方案1】:

这是反向打印链接列表的代码,您应该能够通过传入 char 缓冲区参数来针对您的 concat 问题对其进行修改。

void ReversePrint(Node *head)

  if (head == NULL)
      return;
  else if (head->next == NULL) 
      printf("%d\n", head->data);
   else 
      ReversePrint(head->next);
      printf("%d\n", head->data);
  

诀窍是在执行实际工作之前进行递归调用,以便从最后一个节点开始处理,然后在展开时处理其他所有内容。然后你可以避免左手连接问题,你只需像往常一样在末尾连接即可。

【讨论】:

这需要链表的元素数量呈线性关系的空间。这通常是不可接受的。 空间,用于调用堆栈,还是用于存储缓冲区? 调用堆栈上的空间。【参考方案2】:

我做了一个简单的工作台来检查哪种方法更有效:

在每次迭代中使用realloc 遍历列表两次,第一次获取大小,第二次复制字符串

我在这个工作台中使用的列表元素是一个结构:

struct elem

    char *str;
    size_t size;
;

这是一个双向链表的元素(我使用了this这个列表的实现)。

然后我以这种方式生成了一些字符串:

for(int j = 0; j < i; j ++)

    char * str = malloc(SINGLE_STRING_LEN + 1);
    memset(str, gimme_next_char(), SINGLE_STRING_LEN);
    str[SINGLE_STRING_LEN] = '\0';

    struct elem e;
    e.str = str;
    e.size = SINGLE_STRING_LEN;

    dl_list_insert_at_tail(&l, (void *) &e);

SINGLE_STRING_LEN 具有值10gimme_next_char 循环返回从AZ 的字符。 i 是来自外部循环的值,它允许控制将多少项插入到列表中。我测试了 100、200、...、900 个元素的操作。

第一种方法如下所示:

char *concat_str = NULL;
size_t concat_str_len = 1; // Remember about '\0' character
for_each_in_dl_list(struct elem, e, l)

    size_t new_len = concat_str_len + e->size;
    concat_str = realloc(concat_str, new_len);
    strcpy(&concat_str[concat_str_len - 1], e->str);
    concat_str_len = new_len;

(我假设sizeof(char) == 1)。 正如您在每次迭代中看到的那样,realloc 用于调整字符串的大小。然后使用strcpy 将列表中的字符串附加到结果字符串中。基本上你到达那里O(N * (M + realloc complexity)) 其中Mstrcpy 函数复杂度。

第二种方法:

size_t concat_str_len = 1;
for_each_in_dl_list(struct elem, e, l)
    concat_str_len += e->size;

char *concat_str = malloc(concat_str_len);
concat_str_len = 1;
for_each_in_dl_list(struct elem, e, l)

    size_t new_len = concat_str_len + e->size;
    strcpy(&concat_str[concat_str_len - 1], e->str);
    concat_str_len = new_len;

这里我们首先获取整个最终字符串的大小,然后遍历列表以附加列表中的每个字符串。你到了O(N + N * M + malloc complexity)

必须释放动态分配的数据,但我不想在此处粘贴此代码,因为它在本主题中无用。

我已经调用了第一个程序和第二个程序大约 20 次来计算平均执行时间:

Elems on the list    |  105  |  305  |  505  |  705  |  905
First approach [us]  |   9   |  25   |  42   |  54   |   67
Second approach [us] |   6   |  14   |  25   |  31   |   39

第二种方法更快。我还可以看到,第一种方法的标准偏差要大得多(列表中的 905 个元素大约大 6 倍)。这可能是多次调用realloc 函数的原因,因为第一种方法更依赖于系统。

【讨论】:

【参考方案3】:

如果你不介意做一些丑陋的指针,我会考虑下面的代码。

struct Ptr 
    char *alloc_;
    char *start_;
;
struct Ptr concat(Node *head) 
    Ptr ptr;
    ptr.alloc_ = malloc(maxSizeOfCharArray);
    ptr.start_ = ptr.alloc_ + maxSizeOfCharArray - 2;
    *(ptr.start_ + 1) = 0;
    while (head != NULL)
        ptr.start_ -= strlen(head->str)
        memcpy(ptr.start_, head->str, strlen(head->str));
        head = head->next;
    
    return ptr;

在所有这些之后,您有责任释放 alloc_ 内存。不知道它是否会比天真的解决方案更快。

【讨论】:

以上是关于有效地连接左侧的 N 个字符数组,C的主要内容,如果未能解决你的问题,请参考以下文章

c语言中如何获得字符串的第n个字符?

c语言如果用字符串类型输出字符数组,字符数组最后一个是0,那么为啥会出现乱码

在 .NET 中有效地合并字符串数组,保持不同的值

N组相同固定长度字符数组成员统计 C/C++语言实现

C语言 字符数组在定义时实际长度能不能等于有效长度 不给\0留空间

在 C# 中有效地将字符串转换为字节数组(不使用编码)[重复]