这个递归C代码如何工作?

时间:2012-04-15 14:40:35

标签: c recursion

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

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循环来反转数组。但是这段代码让我对递归感到困惑。

任何人都可以解释上面的代码吗?

6 个答案:

答案 0 :(得分:3)

等同于

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)相当于递归限制器(原文中的错误)。

答案 1 :(得分:1)

这个算法背后的想法是每一步:

- :交换数组的最后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

答案 2 :(得分:1)

Simplified Crazy walk trough;

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 */
}

给定char数组"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.

答案 3 :(得分:0)

要认识到的重要一点是a是指向数组第一个元素的指针,因此a&a[0]相同。 &a[1]是指向数组第二个元素的指针。因此,如果您使用&a[1]作为参数调用该函数,它将适用于以第二个元素开头的子数组。

答案 4 :(得分:0)

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

答案 5 :(得分:0)

必须使用以下方法调用该函数:

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

第一个'if'检查数组是否至少包含两个元素。接下来,函数的作用是交换数组的第一个和最后一个元素的位置。

递归调用会更改下一步必须工作的边界。它将数组的开头递增一个位置,并将数组的末尾减少一个位置;因为在这次迭代中这两个元素已被颠倒过来。