递归导致的分段错误

Segmentation fault due to recursion

提问人:Slayter 提问时间:2/28/2013 最后编辑:KaunteyaSlayter 更新时间:3/1/2013 访问量:6338

问:

我正在编写一个程序,该程序将取一个介于 1-10 之间的数字并显示排列数字的所有可能方法。

前任 输入:3 输出:

   1 2 3
   1 3 2
   2 1 3
   2 3 1
   3 1 2
   3 2 1

每当我输入 9 或 10 时,程序都会给出分段错误并转储内核。我相信问题是我的递归算法被调用了太多次。有人可以帮忙指出我如何限制必要的递归调用量吗?这是我目前的代码:

void rearange(int numbers[11], int index, int num, int fact) {

  int temp = numbers[index];
  numbers[index] = numbers[index-1];
  numbers[index-1] = temp;

  int i;
  for (i = 1; i <= num; ++i) // print the current sequence
  {
    printf("%d ", numbers[i]);
  }
  printf("\n");

  fact--;  // decrement how many sequences remain
  index--; // decrement our index in the array

  if (index == 1) // if we're at the beginning of the array
    index = num;    // reset index to end of the array

  if (fact > 0) // If we have more sequences remaining
    rearange(numbers, index, num, fact);    // Do it all again! :D
}

int main() {
  int num, i; // our number and a counter

  printf("Enter a number less than 10: ");
  scanf("%d", &num); // get the number from the user

  int numbers[11]; // create an array of appropriate size
  // fill array
  for (i = 1; i <= num; i++) { // fill the array from 1 to num
    numbers[i] = i;
  }

  int fact = 1; // calculate the factorial to determine
  for (i = 1; i <= num; ++i) // how many possible sequences
  {
    fact = fact * i;
  }

  rearange(numbers, num, num, fact); // begin rearranging by recursion

  return 0;
}
C 递归 分段 - 故障 序列 coredump

评论

0赞 abelenky 2/28/2013
GDB 应该告诉您 seg-fault/core dump 发生的位置,以及崩溃发生时堆栈的深度。它说了什么?
0赞 Slayter 2/28/2013
fact 变量显示还剩下多少次迭代。当输入 9 时,程序崩溃时还剩下 188202 次迭代。它说它发生在语句期间。printf()
0赞 abelenky 2/28/2013
您正在避免使用 GDB。调试器的使用在编程中是必不可少的。如果你有一个核心文件,你应该真正学会在GDB中加载它,并检查堆栈深度和崩溃位置等内容。
0赞 Bernd Elkemann 2/28/2013
@Slayter不知道你是否还在那里,但请试试我在“编辑 2”中写的代码:它以等于项目数(例如 10)的递归深度解决了您的问题。
0赞 Slayter 2/28/2013
我正在使用 GDB 来获取该信息

答:

6赞 P.P 2/28/2013 #1

9!(362880) 和 (3628800) 是巨大的数字,当您执行尽可能多的递归调用时,它们会溢出调用堆栈。因为所有的局部变量和形式参数都必须存储。您要么必须增加堆栈大小,要么将递归转换为迭代。10!

在 linux 上,您可以执行以下操作:

ulimit -s unlimited

将堆栈大小设置为无限制。默认值通常为 8MB。

评论

0赞 Marcin Waśniowski 2/28/2013
然后你按值传递表,给你 11 * 4 字节的数组和另一个 3*4 的参数......堆栈上留下了大量内存。我认为重新设计概念会比调整堆栈大小要好得多。
0赞 Slayter 2/28/2013
我怎样才能将其转换为迭代。这是一个学校项目,所以我不确定增加堆栈大小是否是一种选择
0赞 Marcin Waśniowski 2/28/2013
@Slayter 1.传递指针或引用,而不是数组 2。我认为你可以在循环中调用它,因为你不使用最后一遍的结果,所以你不需要重复
0赞 Slayter 2/28/2013
重复性是此作业的要求:/
0赞 Marcin Waśniowski 2/28/2013
会很难,10!堆栈开销为 4 字节的调用,仅递归就超过 13MB。只是一个提示,因为这里已经很晚了。是否允许在一次调用中打印多个组合?
1赞 chue x 2/28/2013 #2

我会让你的函数迭代 - 添加,并删除递归调用:rearangedo while

void rearange(int numbers[11], int index, int num, int fact) {
    int temp;
    do
    {
      temp = numbers[index];
      numbers[index] = numbers[index-1];
      numbers[index-1] = temp;

      int i;
      for (i = 1; i <= num; ++i) // print the current sequence
      {
        printf("%d ", numbers[i]);
      }
      printf("\n");

      fact--;  // decrement how many sequences remain
      index--; // decrement our index in the array

      if (index == 1) // if we're at the beginning of the array
        index = num;    // reset index to end of the array

    } while (fact > 0);
}

评论

0赞 Slayter 2/28/2013
虽然这可能有效,但这是需要递归的学校作业。
0赞 V-X 2/28/2013 #3

这不是深度递归的任务。 尝试发明一些对堆栈更友好的算法。 跟随代码在速度方面比在堆栈大小方面有相当大的麻烦...... 它有点慢,例如 n=1000,但它有效。

#include <stdio.h>

void print_arrangement(int n, int* x)
{
  int i;
  for(i = 0; i < n; i++)
  {
  printf("%s%d", i ? " " : "", x[i]);
  }
  printf("\n");
}

void generate_arrangements(int n, int k, int* x)
{
    int i;
    int j;
    int found;

    if (n == k)
    {
        print_arrangement(n, x);
    }
    else
    {
    for(i = 1; i <= n; i++)
    {
        found = 0;
        for(j = 0; j < k; j++)
        {
            if (x[j] == i)
            {
                found = 1;
            }
        }
        if (!found)
        {
            x[k] = i;
            generate_arrangements(n, k + 1, x);
        }
    }   
    }
}

int main(int argc, char **argv)
{
  int x[50];
  generate_arrangements(50, 0, x);
}
2赞 Bernd Elkemann 2/28/2013 #4

计算排列可以迭代完成,但即使你以递归方式进行计算,也不需要有一个巨大的堆栈(比如建议增加系统堆栈的答案)。事实上,你只需要一小部分的堆栈。考虑一下:

0 1      <- this needs **2** stackframes 
1 0                and an for-loop of size 2 in each stackframe

0 1 2    <- this needs **3** stackframes 
0 2 1              and an for-loop of size 3 in each stackframe
1 0 2
1 2 0
2 1 0
2 0 1

置换 9 个元素需要 9 个堆栈帧和一个 for 循环,每个堆栈帧中有 9 个元素。

编辑:我冒昧地在您的 rerange-function 中添加了一个递归计数器,它现在打印如下:

Enter a number less than 10: 4
depth 1      1 2 4 3
depth 2      1 4 2 3
depth 3      4 1 2 3
depth 4      4 1 3 2
depth 5      4 3 1 2
depth 6      3 4 1 2
depth 7      3 4 2 1
depth 8      3 2 4 1
depth 9      2 3 4 1
depth 10      2 3 1 4
depth 11      2 1 3 4
depth 12      1 2 3 4
depth 13      1 2 4 3
depth 14      1 4 2 3
depth 15      4 1 2 3
depth 16      4 1 3 2  which is obviously wrong even if you do it recursively.
depth 17      4 3 1 2
depth 18      3 4 1 2
depth 19      3 4 2 1
depth 20      3 2 4 1
depth 21      2 3 4 1
depth 22      2 3 1 4
depth 23      2 1 3 4
depth 24      1 2 3 4
....

递归叶应该是唯一输出的,因此深度应该是恒定的和小的(等于您输入的数字)。

编辑2:

好的,写了代码。试试看:

#include "stdio.h"
void betterRecursion(int depth, int elems, int* temp) {
    if(depth==elems) {
        int j=0;for(;j<elems;++j){
            printf("%i ", temp[j]);
        }
        printf("   (at recursion depth %u)\n", depth);
    } else {
        int i=0;for(;i<elems;++i){
            temp[depth] = i;
            betterRecursion(depth+1, elems, temp);
        }
    }
}
int main() {
    int temp[100];
    betterRecursion(0, 11, temp); // arrange the 11 elements 0...10
    return 0;
}

评论

0赞 Bernd Elkemann 2/28/2013
@KingsIndian 让我写一些代码来尝试一下。如果是正确的,我当然会投赞成票。
1赞 Marcin Waśniowski 2/28/2013
@KingsIndian eznme 是对的,如果你选择“分而治之”女巫是递归的主要概念,那么你不需要超过用户输入的帧堆栈数量。如果 num = 1,则只输出 {1},如果 num 为 2,则输出 {1 2},交换并输出 {2 1},如果是 3.。然后首先打印 val {1},然后递归尝试打印表格的其余部分......等等......这应该有效
0赞 Bernd Elkemann 2/28/2013
好的写了代码。递归深度等于条目数。
0赞 P.P 2/28/2013
@eznme 你说的是你的.您是否说 OP 的代码可以在 *MB 或更小的堆栈大小下使用 10 的排列,即 10!?我的回答说它会坏,你说它不会。您是否尝试过使用 8MB 或更小的堆栈?这对我来说似乎太明显了,无法争论。betterRecursion
0赞 Bernd Elkemann 2/28/2013
@KingsIndian 试试我的代码。阶乘在这里没有意义,您不需要一次将所有组合存储在堆栈上,只需将当前组合存储在堆栈上即可。
0赞 janos 2/28/2013 #5

您的程序不必要地使用了过多的递归。它使用递归,而实际上就足够了。n!n

若要仅使用递归,请考虑递归函数的以下逻辑:n

  • 它接收要排列的唯一数字数组nums[]n
  • 这些排列可以有不同的第一个数字,因为数组中有不同的数字nn
  • (关键步骤)遍历 的元素,并在每次迭代中创建一个新数组,但删除了当前元素,并递归到相同的函数中,将这个较短的数组作为参数传递nums[]
  • 随着递归的深入,参数数组会越来越小
  • 当只剩下一个元素时,递归就结束了

使用此算法,您的递归不会比更深,并且您不会得到分段错误。关键在关键步骤中,您可以在其中构建一个新的数字数组,该数组始终比输入数组短 1 个项目。n

顺便说一句,请确保在提交之前检查程序的输出,例如运行它以确保您获得正确数量的组合,并检查没有重复项(如果没有重复项,则输出应为空)。| sort | uniq | wc -l| sort | uniq -d

剧透警告:这是我使用上述算法的变体的实现:https://gist.github.com/janosgyerik/5063197C++

评论

0赞 Slayter 3/1/2013
在运行您的代码时,我看到它给出了与我的相同的输出。我的只是顺序不同,这不是任务的要求。我的输出中没有重复的行(但是问题中列出的输出是我教授的)。我知道我不需要那么多递归,这就是问题的原因。此外,我应该在问题中明确表示,我不需要有人重写我的代码,因为这是学校的作业,我只是在寻找一些建议。谢谢。
0赞 Slayter 3/1/2013
我已经重写了我的代码,对于这个问题来说可能有点太复杂了,但凭借我在编程方面的经验,它对我有用。这里是:链接