【问题标题】:Stack overflow due to deep recursion由于深度递归导致的堆栈溢出
【发布时间】:2019-12-21 14:18:54
【问题描述】:

我有一个任务是解决硬币找零问题,如下所示: 我有 5 种硬币 {1,5,10,25,50},我们想用这些硬币换成给定的金额,例如,我可以用多少种方式形成 11 美分,或 n 美分一般的 ?我的程序应该能够处理高达 7489 美分的输入。

我使用递归和动态编程来解决问题,但在运行时 (input >= 3980) 出现堆栈溢出错误

我该如何解决这个错误?

#include <iostream>
using namespace std;
typedef long long ll;
int Cents[5] = { 1,5,10,25,50 }, in, mem[5][7495];

ll CC(int i, int val) { // this is where the recursion happens
    if (val == 0)   return 1;
    if (i == 5) return 0;
    if (mem[i][val] != -1) return mem[i][val];
    long long op1 = CC(i + 1, val);
    long long op2 = Cents[i] <= val ? CC(i, val - Cents[i]) : 0;
    return  mem[i][val] = (op1+op2);
}

int main() { // here i pass an input and fill the mem 2d array with -1's
    scanf_s("%d", &in);
    memset(mem, -1, sizeof mem);
    printf("%lld\n", CC(0, in));
    system("pause");
    return 0;
}

例如,如果输入是 5,那么输出应该是 2 路 (1),(5) 如果 11 ,则 4 (1*11),(1*6,5*1),(10*1,1*1),(1*1,5*2) 等等。

【问题讨论】:

  • 两种解决方案:1)使用自下而上的方法,而不是自上而下。 2)检查有关命令行参数的课程说明。它们的堆栈大小可能大于默认值(例如,请参阅此答案:stackoverflow.com/a/2275586/2956272)。
  • 输入n 的递归深度与n 成正比,因为您试图更改n 便士。这样的做法是不合理的。
  • 你可以remove the recursion
  • memset(mem, -1, sizeof mem)mem 中的每个 byte 设置为 -1。不能保证这将导致 mem 数组中的每个 int 值都具有值 -1。
  • 您的作业是否要求您使用递归?在循环中进行计算更简单、更灵活。

标签: c++ recursion dynamic-programming stack-overflow


【解决方案1】:

您有堆栈溢出,因为您的递归深度仅受数组大小和便士数的限制。而且您的堆栈状态的大小是合理的。

一种解决方案是使用显式堆栈。

enum coins {
  penny, nickle, dime, quarter, half,
  coin_count
};
enum {
  max_cash = 7495
};
struct coin_problem {
  coins lowest_coin = penny;
  int money = 0;
};
struct coin_cache {
  unsigned long long solution[coin_count][max_cash+1];
  coin_cache(){
    for(auto& row:solution)
      for(auto& val:row)
        val=-1;
  }
  unsigned long long& set(coin_problem p){
    return solution[p.lowest_coin][p.money];
  }
  unsigned long long get(coin_problem p)const{
    if(p.lowest_coin==coin_count) return 0;
    if(p.money==0) return 0;
    return solution[p.lowest_coin][p.money];
  }
};
unsigned long long count_solutions( std::vector<coin_problem>& vec, coin_cache& cache){
  unsinged long lon retval = 0;
  while(!vec.empty()){
    auto cur = vec.back();
    retval = cache.get[cur];
    if (retval != -1) {
      vec.pop_back();
      continue;
    }
    ++cur.lowest_coin;
    auto op1 = cache.get[cur];
    if (op1 == -1) vec.push_back(cur);
    --cur.lowest_coin;
    unsigned long long op2 = -1;
    if(Cents[cur.lowest_coin] <= cur.money){
      cur.money -= Cents[cur.lowest_coin];
      op2 = cache.get[cur];
      if (op2==-1) vec.push_back(cur);
      cur.money += Cents[cur.lowest_coin];
    } else {
      op2 = 0;
    }
    if (op1 != -1 && op2 != -1){
      retval = cache.set(cur) = op1+op2;
      vec.pop_back();
    }
  }
  return retval;
}
unsigned long long count_solutions( coin_problem p ){
  auto pcache = std::make_unique<coin_cache>();
  std::vector<coin_problem> todo = {p};
  return count_solutions( todo, *pcache );
}

我撕掉了你的递归解决方案,并给了它一个手动堆栈。

对于问题堆栈中的每个条目,我们首先查看缓存中是否有解决方案等待。如果是这样,我们计算它,并将其存储在缓存中,然后弹出问题。如果问题堆栈为空,我们将其返回。

如果不是,我们将首先要解决的子问题压入栈顶,让我们当前的问题稍后解决,然后循环直到堆栈为空。

由于我们的手动堆栈存在于免费存储(也称为堆)中,因此我们不太可能在桌面系统上遇到堆栈溢出。

这不是很有效,因为对于每个问题,我们可以在堆栈上拥有多个条目。但这至多应该是一个不变的因素,并且可以通过重新排序“递归”以首先执行“最深”调用来解决。或者使问题堆栈成为按深度排序的一组问题。

另一种方法是只解决表中的每个条目,最深的优先。

【讨论】:

  • 非常感谢您的帮助!
猜你喜欢
  • 2010-10-26
  • 2012-07-24
  • 2021-01-26
  • 2011-02-26
  • 1970-01-01
  • 2013-04-05
相关资源
最近更新 更多