有效地连接左侧的 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
具有值10
和gimme_next_char
循环返回从A
到Z
的字符。 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))
其中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 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语言如果用字符串类型输出字符数组,字符数组最后一个是0,那么为啥会出现乱码