ran*_*dom 6 c string recursion
你如何解决涉及递归的以下问题?
使用prototype实现一个函数, char *repeat(char *s, int n)
以便创建并返回一个由 n个重复的输入字符串组成的字符串.例如:如果输入为"Hello"且为3,则输出为"HelloHelloHello".仅使用递归构造.
我的解决方案在我看来非常难看,我正在寻找更清洁的东西.这是我的代码:
char *repeat(char *s, int n) {
if(n==0) {
char *ris = malloc(1);
ris[0] = '\0';
return ris;
}
int a = strlen(s);
char *ris = malloc(n*a+1);
char *ris_pre = repeat(s,n-1);
strcpy(ris,ris_pre);
strcpy(ris+(n-1)*a,s);
free(ris_pre);
return ris;
}
Run Code Online (Sandbox Code Playgroud)
更加整洁和优雅的解决方案(我称之为基本解决方案)如下:
基本解决方案
char *internalRepeat(char *s, int n, size_t total)
{
return (n > 0)
? strcat(internalRepeat(s, n - 1, total + strlen(s)), s)
: strcpy(malloc(total + 1), "");
}
char *repeat(char *s, int n)
{
return internalRepeat(s, n, 0);
}
Run Code Online (Sandbox Code Playgroud)
这是递归之美.此解决方案的关键是使用递归来递增地构建结果的长度.参数total
执行此操作(不包括NUL终止符).当递归终止时,结果缓冲区被分配一次(包括NUL终止符),然后我们使用递归展开将每个副本附加s
到结果中.基本解决方案的行为如下:
如果基于以上函数创建程序,则以下语句:
printf("Repeat \"\" 0 times: [%s]\n", repeat("", 0));
printf("Repeat \"\" 3 times: [%s]\n", repeat("", 3));
printf("Repeat \"abcde\" 0 times: [%s]\n", repeat("abcde", 0));
printf("Repeat \"abcde\" 1 times: [%s]\n", repeat("abcde", 1));
printf("Repeat \"abcde\" 4 times: [%s]\n", repeat("abcde", 4));
Run Code Online (Sandbox Code Playgroud)
将产生以下输出:
Repeat "" 0 times: []
Repeat "" 3 times: []
Repeat "abcde" 0 times: []
Repeat "abcde" 1 times: [abcde]
Repeat "abcde" 4 times: [abcdeabcdeabcdeabcde]
Run Code Online (Sandbox Code Playgroud)
编辑:优化解决方案如下.如果您对优化技术感兴趣,请继续阅读.
此处的所有其他提议主要在O(n ^ 2)中运行,并在每次迭代时分配内存.尽管Basic Solution很优雅,只使用一个malloc()
,只需要两个语句,但令人惊讶的是Basic Solution的运行时间也是O(n ^ 2).如果字符串s
很长,这会使效率非常低,这意味着基本解决方案不比此处的任何其他提议更有效.
优化的解决方案
以下是实际在O(n)中运行的此问题的最佳解决方案:
char *internalRepeat(char *s, int n, size_t total, size_t len)
{
return (n > 0)
? strcpy(internalRepeat(s, n - 1, total, len), s) + len
: strcpy(malloc(total + 1), "");
}
char *repeat(char *s, int n)
{
int len = strlen(s);
return internalRepeat(s, n, n * len, len) - (n * len);
}
Run Code Online (Sandbox Code Playgroud)
如您所见,它现在有三个语句,并使用一个参数len
来缓存长度s
.它使用递归len
来计算结果缓冲器,其中内的位置n
"的第副本s
将被定位,因此使我们能够替换strcat()
与strcpy()
每个时间s
被添加到结果.这给出了O(n)的实际运行时间,而不是O(n ^ 2).
基本和优化解决方案之间有什么区别?
所有其他解决方案strcat()
至少n
使用字符串s
来附加结果的n
副本s
.这就是问题所在,因为实施strcat()
隐藏效率低下.在内部,strcat()
可以被认为是:
strcat = strlen + strcpy
Run Code Online (Sandbox Code Playgroud)
也就是说,在追加时,首先必须找到你要追加的字符串的结尾,然后再进行追加.这种隐藏的开销意味着,实际上,创建n
字符串的副本需要n
长度检查和n
物理复制操作.然而,真正的问题在于,对于s
我们附加的每个副本,我们的结果会变得更长.这意味着,在每个连续的长度检测strcat()
的结果也变得越来越长.如果我们现在使用" 我们必须扫描或复制的次数s
"比较两种解决方案作为比较的基础,我们可以看出两种解决方案的区别在哪里.
对于n
字符串的副本s
,Basic Solution执行如下操作:
strlen's/iteration: 2
strcpy's/iteration: 1
Iteration | Init | 1 | 2 | 3 | 4 | ... | n | Total |
----------+------+---+---+---+---+-----+---+------------+
Scan "s" | 0 | 1 | 2 | 3 | 4 | ... | n | (n+1)(n/2) |
Copy "s" | 0 | 1 | 1 | 1 | 1 | ... | 1 | n |
Run Code Online (Sandbox Code Playgroud)
而优化解决方案的执行方式如下:
strlen's/iteration: 0
strcpy's/iteration: 1
Iteration | Init | 1 | 2 | 3 | 4 | ... | n | Total |
----------+------+---+---+---+---+-----+---+------------+
Scan "s" | 1 | 0 | 0 | 0 | 0 | ... | 0 | 1 |
Copy "s" | 0 | 1 | 1 | 1 | 1 | ... | 1 | n |
Run Code Online (Sandbox Code Playgroud)
从表中可以看出,由于内置长度检入,基本解决方案对字符串执行(n ^ 2 + n)/ 2次扫描strcat()
,而优化解决方案始终执行(n + 1)次扫描.这就是基本解决方案(以及依赖的所有其他解决方案strcat()
)在O(n ^ 2)中执行的原因,而优化解决方案在O(n)中执行.
O(n)与O(n ^ 2)的实际比较如何?
使用大字符串时,运行时间会产生巨大差异.作为一个例子,让我们采用s
1MB 的字符串,我们希望创建1,000个副本(== 1GB).如果我们有1GHz CPU可以扫描或复制1个字节/时钟周期,那么s
将生成1,000个副本,如下所示:
注意:n取自上面的性能表,并代表s的单次扫描.
Basic: (n + 1) * (n / 2) + n = (n ^ 2) / 2 + (3n / 2)
= (10^3 ^ 2) / 2 + (3 * 10^3) / 2
= (5 * 10^5) + (1.5 * 10^2)
= ~(5 * 10^5) (scans of "s")
= ~(5 * 10^5 * 10^6) (bytes scanned/copied)
= ~500 seconds (@1GHz, 8 mins 20 secs).
Optimised: (n + 1) = 10^3 + 1
= ~10^3 (scans of "s")
= ~10^3 * 10^6 (bytes scanned/copied)
= 1 second (@1Ghz)
Run Code Online (Sandbox Code Playgroud)
如您所见,优化解决方案几乎立即完成,拆除基本解决方案,需要将近10分钟才能完成.但是,如果您认为将字符串s
缩小会有所帮助,那么下一个结果将会让您感到恐惧.同样,在处理1个字节/时钟周期的1GHz机器上,我们采用s
1KB(小1千倍),并制作1,000,000份(总数== 1GB,与之前相同).这给出了:
Basic: (n + 1) * (n / 2) + n = (n ^ 2) / 2 + (3n / 2)
= (10^6 ^ 2) / 2 + (3 * 10^6) / 2
= (5 * 10^11) + (1.5 * 10^5)
= ~(5 * 10^11) (scans of "s")
= ~(5 * 10^11 * 10^3) (bytes scanned/copied)
= ~50,000 seconds (@1GHz, 833 mins)
= 13hrs, 53mins, 20 secs
Optimised: (n + 1) = 10^6 + 1
= ~10^6 (scans of "s")
= ~10^6 * 10^3 (bytes scanned/copied)
= 1 second (@1Ghz)
Run Code Online (Sandbox Code Playgroud)
这是一个真正令人震惊的差异.优化的解决方案与之前同时执行,因为写入的数据总量相同.但是,基本解决方案停止了半天以上的结果.这是O(n)和O(n ^ 2)之间运行时间的差异.
尝试这种仅分配字符串一次的方法:
char *repeat(char *s, int n) {
int srcLength = strlen(s);
int destLength = srcLength * n + 1;
char *result = malloc(destLength);
result[0] = '\0'; // This is for strcat calls to work properly
return repeatInternal(s, result, n);
}
char *repeatInternal(char *s, char *result, int n) {
if(n==0) {
return result;
}
strcat(s, result);
return repeat(result, s, n-1);
}
Run Code Online (Sandbox Code Playgroud)
第二个重复方法只能由第一个重复方法使用。(第一个是你的原型方法)
注意:我没有编译/测试它,但这应该可以工作。
归档时间: |
|
查看次数: |
1539 次 |
最近记录: |