有很多递归问题,我基本上理解一些简单的递归算法,例如数组元素的总和。但是,我的朋友给了我这个反转数组的代码:
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循环来反转数组。但是这段代码让我对递归感到困惑。
任何人都可以解释上面的代码吗?
答案 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})等等
#::::::::::::::::::::::::::::::::::::::::::::::::::::
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)
必须使用以下方法调用该函数:
第一个'if'检查数组是否至少包含两个元素。接下来,函数的作用是交换数组的第一个和最后一个元素的位置。
递归调用会更改下一步必须工作的边界。它将数组的开头递增一个位置,并将数组的末尾减少一个位置;因为在这次迭代中这两个元素已被颠倒过来。