我正在学习如何将递归应用于数组。
例如,我通常以这种方式读取数组:
void read_array(int *a, int n){
int i;
for(i = 0; i < n; ++i)
scanf("%d", &a[i]);
return;
}
我想以递归方式读取数组。我写了以下函数:
void read_array(int *a, int n){
int i = n - 1;
if (n < 0)
return;
else{
if(scanf("%d", &a[n - 1 - i]) == 1){
read_array(a, n - 1);
return;
}
}
}
它编译,但在运行时会产生分段错误错误。它使我感到困惑,因为该函数考虑了一个应该阻止它的基础案例0
。
答案 0 :(得分:7)
您对数组索引的计算是错误的。这一行:
if(scanf("%d", &a[n - 1 - i]) == 1){
假设{em>初始值为n
,但与此同时,每次递归步骤减少 n
。话虽如此,它不应该崩溃但只是反复写出a
的第一个元素,因为i = n - 1
n - 1 - i
总是为零。
编写这种递归的惯用方法是递归i
:
void read_array(int *a, int n, int i)
{
if (i < n)
{
if(scanf("%d", &a[i]) == 1)
{
read_array(a, n, i+1);
}
}
}
并使用i
的初始值调用它,例如read_array(a, 10, 0)
用于读取10个元素的数组。
在实践中,应避免使用C语言中的递归。 *
*函数式语言通常可以优化递归,C只是使用带有大量开销的调用堆栈。
在这个例子中,写一个 pure 函数的递归的理论目的在一定程度上被一个返回void
的函数所击败。如果这只是学习原理,那么函数实际上应该返回一些东西。例如,您可以创建一个功能“列表构建器”:
#include <stdio.h>
#include <stdlib.h>
// place the side effect in a separate function
int getValue(void)
{
// could have `scanf()` here:
return rand();
}
typedef struct List
{
int a[10];
size_t length;
} List;
// non-functional helper to get around limitations of C:
// (if it could initialize result directly with the new values, it would
// be functional)
List listAppend(List list, int val)
{
List result = list;
result.a[result.length++] = val;
return result;
}
// recursive function without side effects:
List buildList(List list, int (*value)())
{
if (list.length >= 10) return list;
return buildList(listAppend(list, value()), value);
}
int main(void)
{
List myList = buildList((List){0}, &getValue);
for (size_t i = 0; i < myList.length; ++i)
{
printf("myList.a[%zu] is %d\n", i, myList.a[i]);
}
}
答案 1 :(得分:3)
该功能存在错误。
由于变量i
按以下方式初始化
int i = n - 1;
然后是此调用中的第二个参数
scanf("%d", &a[n - 1 - i])
评估为
scanf("%d", &a[n - 1 - (n - 1)])
它总是等于零
scanf("%d", &a[0])
由于使用指针a
的相同值调用递归函数,因此所有输入的值都将分配给a[0]
。数组的所有其他元素仍然未初始化。
虽然这不是该功能异常执行的原因。
有可能是使用了一个大数组而且堆栈太小而无法递归调用该函数。
在任何情况下,都可以通过以下方式更简单,更正确地定义函数
size_t read_array( int *a, size_t n )
{
return n && scanf( "%d", a ) == 1 ? 1 + read_array( a + 1, n - 1 ) : 0;
}
考虑到输入可以被用户中断。在这种情况下,该函数返回数组的初始化元素的数量。
这是一个示范程序。
#include <stdio.h>
size_t read_array( int *a, size_t n )
{
return n && scanf( "%d", a ) == 1 ? 1 + read_array( a + 1, n - 1 ) : 0;
}
#define N 10
int main(void)
{
int a[N];
size_t n = read_array( a, N );
for ( size_t i = 0; i < n; i++ ) printf( "%d ", a[i] );
putchar( '\n' );
return 0;
}
如果要输入数字序列
0 1 2 3 4 5 6 7 8 9
然后输出
0 1 2 3 4 5 6 7 8 9
答案 2 :(得分:1)
示例:
int read_array_aux(int *i, int *n) {
if (i == n) {
return 0;
}
if (scanf("%d", i) != 1) {
return -1;
}
return read_array_aux(i + 1, n);
}
int read_array_aux2(int *a, size_t i, size_t n) {
if (i == n) {
return 0;
}
if (scanf("%d", a + i) != 1) {
return -1;
}
return read_array_aux2(a, i + 1, n);
}
int read_array(int *a, size_t n) {
return read_array_aux(a, a + n);
// return read_array_aux2(a, 0, n);
}
答案 3 :(得分:1)
首先,条件void read_array() {
read_head();
read_tail();
}
是错误的。可能这是段错误的原因。
另外,为什么还要为计算索引而烦恼呢?当递归地处理任何类型的列表时,值得掌握列表的头部(列表的第一个元素)和尾部(除头部之外的所有内容)的概念。因此,递归填充数组将定义为(伪代码):
void read_array(int *a, int n) {
if(n<=0) {
return;
} else {
if(scanf("%d", a) == 1) {
read_array(a+1,n-1);
}
}
}
什么是头?它是当前数组的第一个元素。尾巴是什么?数组从下一个元素开始。因此,read_tail相当于read_array,但开头向前移动了一个元素。
最后,将所有内容集中到一个地方:
$(".back, .laptop, .close").click(function(){
console.log("widget closed");
if( !$("body").hasClass('widget-opened')){
$("html").css("overflow-y","auto");
}
});
答案 4 :(得分:1)
正如其他答案所提到的,您对n
的处理会导致问题。您可以从0
的基本情况返回sz == 0
,否则返回下一个递归调用的结果,如果-1
失败,则返回scanf()
。在每次递归调用时,递增a
并递减sz
。应检查调用函数中返回的值是否存在输入错误:成功时为0
,失败时为-1
。
请注意,这是一个tail recursion,应该由大多数优秀的编译器进行优化。
#include <stdio.h>
int read_array(int *a, size_t sz);
int main(void)
{
int arr[5];
puts("Enter array elements:");
if (read_array(arr, 5) != 0) {
fprintf(stderr, "Input error\n");
} else {
for (size_t i = 0; i < 5; i++) {
printf("%8d", arr[i]);
}
putchar('\n');
}
return 0;
}
int read_array(int *a, size_t sz)
{
if (sz == 0 ) {
return 0;
}
if (scanf("%d", a) == 1){
return read_array(a + 1, sz - 1);
} else {
return -1;
}
}
示例互动:
Enter array elements:
1 2 3 4 5
1 2 3 4 5
Enter array elements:
1 2 3 x 5
Input error