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

有效地连接左侧的N个字符数组,C,c,string,concatenation,C,String,Concatenation,假设我有N个要连接在一起的字符数组。每个字符数组存储在一个单独的结构中。为了访问stuct1中的char数组,我需要访问struct2,为了访问struct2,我需要访问struct3,等等(想象一个头在structN,尾在struct1的单链表) 我想连接每个结构中的每个字符数组,以便struct1中的字符数组首先出现,structN中的字符数组最后出现 例如,假设与struct1、struct2和struct3关联的字符数组的内容为“A”、“B”、“C”。我想得到结果字符数组“ABC”。但是

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

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

例如,假设与struct1、struct2和struct3关联的字符数组的内容为“A”、“B”、“C”。我想得到结果字符数组“ABC”。但是,如上所述,要访问structX,我必须首先访问structX+1。因此,将左侧的这些字符数组连接起来会更加有效;我就不必一直检查所有的结构了

有没有一种方法可以在C中高效地实现这一点(例如strcat、snprintf等),或者我必须手动操作每个字符数组以获得我想要的(或者遍历列表,保存指向结构的指针,然后返回)

编辑(清晰度)
假设我有一个单链表。每个元素都有一个字符数组。我想以相反的顺序连接字符数组。有没有一种方法可以做到这一点,而不必两次浏览列表?我知道运行时所有字符数组的最大大小,但我不知道它们各自的大小,直到我访问列表中的每个元素(当我访问元素X时,我知道存储在X中的字符数组的大小)

以下是反向打印链接列表的代码,您应该能够针对concat问题修改它,通过传入char缓冲区参数

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

诀窍是在执行实际工作之前进行递归调用,这样就可以开始处理最后一个节点,然后在展开时处理所有其他节点。然后,您可以避免左手连接问题,只需按照您通常希望的方式在末尾连接即可。

我制作了一个简单的测试台,以检查哪种方法更有效:

  • 在每次迭代中使用
    realloc
  • 在列表上迭代两次,第一次获取大小,第二次复制字符串
我在这个工作台中使用的列表元素是一个结构:

struct elem
{
    char *str;
    size_t size;
};
这是双链接列表的一个元素(我使用了这个列表的实现)

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

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);
}
(我假设
sizeof(char)==1
)。
正如您在每次迭代中看到的那样,
realloc
用于调整字符串大小。然后使用
strcpy
将列表中的字符串追加到结果字符串中。基本上你得到了
O(N*(M+realloc复杂度))
其中
M
strcpy
函数复杂度

第二种方法:

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复杂性)

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

我调用了第一个程序和第二个程序约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倍左右)。这可能是多次调用<代码> ReLoCu函数的原因,因为第一种方法更依赖于系统。

< P>如果你不介意做一些指针丑陋,我会考虑下面的代码。
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;
}

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

N
的最大值是多少?为什么不编写自己的函数将一个字符串前置到另一个字符串呢?确实有可能,但您必须确保复制到的缓冲区足够大,以适合这些对象的内容,或者在每次迭代时使用
realloc
,这可能会很慢。用O(2N)(当你只分配一次内存时)和O(N)方法做一些基准测试,然后你会看到什么是更好的选择。在问题中展示你的结构。解释您尝试了什么。但是您没有说明或显示其他字段之一是否是char数组中字符串的长度(或数组中非字符串-无空终止符-数据的长度)。然而,就我的钱来说,你需要在列表上做这两件事,但与你可能需要做的复制相比,这是一个微不足道的成本。如果不在结构中记录数组的长度,则会给操作增加很多成本。当你创造它们时,你一定知道它们有多长;不要丢弃有价值的信息。不要忘了可以
realloc()
收缩数组。这需要在链表的元素数量上呈线性的空间。这通常是不可接受的。中的空间,用于调用堆栈或存储缓冲区?调用堆栈上的空间。
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;
}