繁体   English   中英

此递归C代码如何工作?

[英]How does this recursive C code work?

递归问题很多,我基本上了解一些简单的递归算法,例如数组元素的总和。 但是,我的朋友给了我这段代码,它反转了一个数组:

void r(int a[], int s)
{
     if(s <=2 ) return;
     int t = a[0];
     a[0] = a[s-1];
     a[s-1] = t;

     r(&a[1], s-2); //  this line confused me, why &a[1]
}

我知道如何使用普通的for循环来反转数组。 但是这段代码确实使我对递归感到困惑。

谁能解释上面的代码行?

相当于

void r(int *arr, size_t len)
{
     for ( ; len >= 2; arr+=1,len-=2 ) {
       int t = arr[0];
       arr[0] = arr[len-1];
       arr[len-1] = t;
       }

}

,其中循环替换了递归调用。 循环的“增量”部分( arr+=1,len-=2 )与递归调用的参数完全相同; 结束条件( len >= 2 )等同于递归停止器(在原始情况下是错误的)。

该算法背后的思想是每一步:

-:交换数组的最后a[s-1]和前a[0]元素:

    int t = a[0];
    a[0] = a[s-1];
    a[s-1] = t;

-:并递归交换中间:

    r(&a[1], s-2);

要理解语法,请记住&a[n]是给定数组的第n+1个元素的地址。 如果您有int *b = &a[1] ,则b[0] == a[1]b[1] == a[2]等。

所以:

  • &a[1]指的是从数组a的第二个元素开始的数组。
  • s - 2表示递归传递的数组的长度短2个元素。

如果您有数组[1 2 3 4 5 6 7 8 9 10] ,则在递归进行时会发生以下情况:

[1 2 3 4 5 6 7 8 9 10] // r(&a[0], 10)
10 [2 3 4 5 6 7 8 9] 1 //   r(&a[1], 8
10 9 [3 4 5 6 7 8] 2 1 //     r(&(&a[1])[1], 6)
10 9 8 [4 5 6 7] 3 2 1 //       r(&(&(&a[1])[1])[1], 4)
10 9 8 7 [5 6] 4 3 2 1 //         r(&(&(&(&a[1])[1])[1])[1], 2)

很酷的事情是,此分析向我们表明终止条件s <= 2是错误的:偶数大小的数组中最里面的2个元素将永远不会被交换。 应该将其更改为s < 2

简化的 疯狂步行槽;

void reverse(int a[], int s)
{
    int temp;              /* temporary value */

    if (s <= 2) return;    /* trigger done */

    t        = a[0];       /* temp = first index of a */
    a[0]     = a[s - 1];   /* a[0] = a[end - 1] (end including \0) */
    a[s - 1] = t;          /* a[end - 1] = temp */

    r(&a[1], s - 2);       /* pass address of a[1] and end - 2 */
}

给定字符数组"ABCDEFG"

简化的内存表可以是:

Address  Value
      7      A
      8      B
      9      C
      a      D
      b      E
      c      F
      d      G

/* Or as used here: */

789abcd <- Simplified memory address
ABCDEFG

我们得到; main()调用reverse(ABCDEFG, 7)

清单1

  • 地址参考 A被压入堆栈(A {BCDEFG})
  • 7个被推入堆栈
  • 调用方的返回地址被压入堆栈
  • 等等
  • 函数调用

和类似的东西

#::::::::::::::::::::::::::::::::::::::::::::::::::::

reverse(ABCDEFG, 7); # Push to STACK 0xB (As List 1)
#====================================================
789abcd <- Memory address.
ABCDEFG <- Values.
0123456 <- Indexes for a in recursion 1.

if (7 <= 2) return;
temp = A
               +     .
a[0] = a[6] => ABCDEFG = GBCDEFG
                     +
a[6] = temp => GBCDEFG = GBCDEFA

reverse(BCDEFA, 5); # Push to STACK 0xC (As in List 1)
#====================================================
 7 89abcd <- Memory addresses.
[G]BCDEFA <- Values
   012345 <- Indexes for a in recursion 2.

if (5 <= 2) return;
temp = B
               +   .
a[0] = a[4] => BCDEFA = FCDEFA
                   +
a[4] = temp => FCDEFA = FCDEBA

reverse(CDEBA, 3); # Push to STACK 0xD (As in List 1)
#====================================================
 78 9abcd <- Memory addresses.
[GF]CDEBA <- Values.
    01234 <- indexes for a in recursion 3.

if (3 <= 2) return;
temp = C
               + .
a[0] = a[2] => CDEBA = EDEBA
                 +
a[2] = temp => EDEBA = EDCBA

reverse(DCBA, 1); # Push to STACK 0xE (As in List 1)
#====================================================
 789 abcd <- Memory addresses.
[GFE]DCBA <- Values.
     0123 <- Indexes for a in recursion 4.
if (1 <= 2) return; YES!

#:::: roll back stack ::::

Pop STACK 0xE
Pop STACK 0xD
Pop STACK 0xC
Pop STACK 0xB
We are back in main() and memory region 789abcd has 
been altered from ABCDEFG to GFEDCBA.

重要的是要意识到a是指向数组第一个元素的指针,因此a&a[0] &a[1]是指向数组第二个元素的指针。 因此,如果以&a[1]作为参数调用该函数,则该函数可在以第二个元素开头的子数组上使用。

&a[1]等于a + 1 ,即指向数组第二个元素的指针。 该函数调用反转数组的“中间” s-2元素。

该函数必须通过以下方式调用:

  • 指向数组第一个元素的指针。 在C语言中,可以使用数组名称进行引用。
  • 数组的大小。

第一个“如果”检查数组是否至少包含两个元素。 接下来,函数要做的是交换数组第一个和最后一个元素的位置。

递归调用更改了下一步必须执行的范围。 它将数组的开头增加一个位置,也将数组的末尾减少一个位置。 因为这两个元素在此迭代中已被反转。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM