在C中递归练习

前端之家收集整理的这篇文章主要介绍了在C中递归练习前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
你如何解决涉及递归的以下问题?

使用原型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;
}

解决方法

更加整洁和优雅的解决方案(我称之为基本解决方案)如下:

基本解决方

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

这是递归之美.此解决方案的关键是使用递归来递增地构建结果的长度.参数total执行此操作(不包括NUL终止符).当递归终止时,结果缓冲区被分配一次(包括NUL终止符),然后我们使用递归展开来附加sto的每个副本结果.基本解决方案的行为如下:

>为任意数量的重复返回零长度字符串
空字符串.
>为非空的零或负迭代返回零长度字符串
串.
>返回非零正数的非零长度字符串
重复非空字符串.

如果基于以上函数创建程序,则以下语句:

printf("Repeat \"\" 0 times: [%s]\n",repeat("",0));
printf("Repeat \"\" 3 times: [%s]\n",3));
printf("Repeat \"abcde\" 0 times: [%s]\n",repeat("abcde",0));
printf("Repeat \"abcde\" 1 times: [%s]\n",1));
printf("Repeat \"abcde\" 4 times: [%s]\n",4));

将产生以下输出

Repeat "" 0 times: []
Repeat "" 3 times: []
Repeat "abcde" 0 times: []
Repeat "abcde" 1 times: [abcde]
Repeat "abcde" 4 times: [abcdeabcdeabcdeabcde]

编辑:优化解决方案如下.如果您对优化技术感兴趣,请继续阅读.

此处的所有其他提议主要在O(n ^ 2)中运行,并在每次迭代时分配内存.即使Basic Solution很优雅,只使用一个malloc(),只需要两个语句,但令人惊讶的是Basic Solution的运行时间也是O(n ^ 2).如果字符串s很长,这会使效率非常低,并且意味着基本解决方案不比此处的任何其他提议更有效.

优化的解决方

以下是实际在O(n)中运行的此问题的最佳解决方案:

char *internalRepeat(char *s,size_t total,size_t len)
{
    return (n > 0)
        ? strcpy(internalRepeat(s,total,len),s) + len
        : strcpy(malloc(total + 1),int n)
{
    int len = strlen(s);

    return internalRepeat(s,n * len,len) - (n * len);
}

如您所见,它现在有三个语句,并使用另一个参数len来缓存s的长度.它递归地使用len来计算结果缓冲区中将定位s的第n个副本的位置,因此允许我们将strcat()替换为strcpy(),每次将s添加到结果中.这给出了O(n)的实际运行时间,而不是O(n ^ 2).

基本和优化解决方案之间有什么区别?

所有其他解决方案都在字符串s上使用strcat()至少n次来将n个s副本附加到结果中.这就是问题所在,因为strcat()的实现隐藏了低效率.在内部,strcat()可以被认为是:

strcat = strlen + strcpy

也就是说,在追加时,首先必须找到你要附加的字符串的结尾,然后再进行追加.这种隐藏的开销意味着,实际上,创建字符串的n个副本需要n个长度检查和n个物理复制操作.然而,真正的问题在于,对于我们附加的每个s副本,我们的结果会变得更长.这意味着结果中strcat()内的每个连续长度检查也会变得更长.如果我们现在使用“我们必须扫描或复制s的次数”比较两种解决方案作为比较的基础,我们可以看出两种解决方案的区别在哪里.

对于字符串s的n个副本,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      |

而优化解决方案的执行方式如下:

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     |

从表中可以看出,由于strcat()中内置的长度检查,Basic Solution对我们的字符串执行(n ^ 2 n)/ 2次扫描,而Optimized Solution总是进行(n 1)次扫描.这就是基本解决方案(以及依赖于strcat()的所有其他解决方案)在O(n ^ 2)中执行的原因,而优化解决方案在O(n)中执行.

O(n)与O(n ^ 2)的实际比较如何?

使用大字符串时,运行时间会产生巨大差异.作为一个例子,让我们采用1MB的字符串,我们希望创建1,000份(== 1GB).如果我们有1GHz cpu可以扫描或复制1个字节/时钟周期,那么将生成1,000个s副本,如下所示:
注意: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)

如您所见,优化解决方案几乎立即完成,拆除基本解决方案,需要将近10分钟才能完成.但是,如果你认为将字符串缩小会有所帮助,那么下一个结果会让你感到恐惧.同样,在处理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)

这是一个真正令人震惊的差异.优化的解决方案与之前同时执行,因为写入的数据总量相同.但是,基本解决方案停止了半天以上的结果.这是O(n)和O(n ^ 2)之间运行时间的差异.

猜你在找的C&C++相关文章