递归引起的分段错误

时间:2013-02-27 21:38:19

标签: c recursion segmentation-fault sequence coredump

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

5 个答案:

答案 0 :(得分:5)

当您执行尽可能多的递归调用时,

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

在linux上,你可以这样做:

ulimit -s unlimited

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

答案 1 :(得分: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;
}

答案 2 :(得分:1)

我会添加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);
}

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

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

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

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

作为旁注,请务必在提交之前检查程序的输出,例如通过| sort | uniq | wc -l运行以确保获得正确数量的组合,并检查没有重复项使用| sort | uniq -d(如果没有重复,则输出应为空。)

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