【问题标题】:Segmentation fault due to recursion递归导致的分段错误
【发布时间】:2013-02-27 21:38:18
【问题描述】:

我正在编写一个程序,该程序将取一个 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;
}

【问题讨论】:

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

标签: c recursion segmentation-fault sequence coredump


【解决方案1】:
当您进行尽可能多的递归调用时,

9! (362880) 和 10! (3628800) 是会溢出 call stack 的巨大数字。因为必须存储所有局部变量和形式参数。您要么必须增加堆栈大小,要么将递归转换为迭代。

在 linux 上,你可以这样做:

ulimit -s unlimited

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

【讨论】:

  • 并且您通过值传递表格,为数组提供 11 * 4 个字节,为参数提供另外 3*4 个字节......堆栈上留下了很多内存。我认为重新设计概念会比调整堆栈大小要好得多。
  • 我怎样才能将其转换为迭代。这是一个学校项目,所以我不确定增加堆栈大小是一种选择
  • @Slayter 1. 传递指针或引用,而不是数组 2. 我认为你可以在循环中调用它,因为你不使用最后一次传递的结果,所以你不需要递归
  • 递归是此作业的要求:/
  • 会很难,10!在堆栈开销上使用 4 字节调用,仅用于递归就提供了超过 13mb。只是一个提示,因为这里已经很晚了。是否允许在一次调用中打印多个组合?
【解决方案2】:

计算排列可以迭代地完成,但即使你递归地做,也不需要有一个巨大的堆栈(比如建议增加你的系统堆栈的答案)。事实上,你只需要很少的筹码量。考虑一下:

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 个堆栈帧,并在每个堆栈帧中通过 9 个元素进行 for 循环。

编辑:我冒昧地为您的重新排列功能添加了一个递归计数器,它现在打印如下:

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;
}

【讨论】:

  • @KingsIndian 让我写一些代码来试试。如果它是正确的,我当然会投票。
  • @KingsIndian eznme 是对的,如果您选择“分而治之”的女巫是递归的主要概念,那么您不需要与用户输入的 num 一样多的帧堆栈。如果 num = 1 则只输出 {1},如果 num 为 2 则输出 {1 2},交换并输出 {2 1},如果是 3.. 则首先打印 val {1},然后递归尝试打印其余部分表......等等......这应该工作
  • 好的,代码写好了。递归深度等于条目数。
  • @eznme 你说的是你的betterRecursion。您是否说 OP 的代码可以使用 *MB 或更小的堆栈大小来实现 10 的排列,即 10! ?我的回答说它会坏,你说它不会。您是否尝试过使用 8MB 或更小的堆栈大小?这对我来说似乎太明显了,无法争论。
  • @KingsIndian 试试我的代码。阶乘在这里没有意义,您不需要一次将所有组合存储在堆栈中,只需存储当前组合即可。
【解决方案3】:

我会让您的 rearange 函数迭代 - 添加 do 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);
}

【讨论】:

  • 虽然这可能有效,但这是一个需要递归的学校作业。
【解决方案4】:

这不是深度递归的任务。 尝试发明一些对堆栈更友好的算法。 以下代码在速度方面比堆栈大小更麻烦...... 它有点慢,例如对于 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);
}

【讨论】:

    【解决方案5】:

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

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

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

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

    附带说明,请确保在提交之前检查程序的输出,例如通过| sort | uniq | wc -l 运行它以确保您获得正确数量的组合,并检查没有与@ 重复的内容987654333@(如果没有重复,则输出应为空)。

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

    【讨论】:

    • 运行您的代码后,我发现它的输出与我的相同。我的顺序不同,这不是任务的要求。我的输出中没有重复的行(但问题中列出的输出是我的教授的)。而且我知道我不需要那么多递归,这就是问题的原因。此外,我应该在问题中明确表示我不需要有人重写我的代码,因为这是一项学校作业,我只是在寻找一些建议。不过还是谢谢。
    • 我已经重写了我的代码,对于这个问题来说它可能有点过于复杂,但是根据我在编程方面的经验,它对我有用。这是:link
    猜你喜欢
    • 1970-01-01
    • 2021-08-15
    • 1970-01-01
    • 2014-10-15
    • 1970-01-01
    • 1970-01-01
    • 2020-05-04
    • 1970-01-01
    相关资源
    最近更新 更多