【问题标题】:Memoization using a map使用地图记忆
【发布时间】:2018-09-28 04:56:54
【问题描述】:

我正在尝试使用 map 优化递归问题来处理运行时错误。但是,使用 memoization 方法和实现 map 仍然不能完全解决问题。通过使用朴素的递归方式,直到totalNum = 36才会出现分段错误错误。优化后,当totalNum变得非常大时,仍然会出现分段错误错误,例如99999。 即使 totalNum 大于 99999,有没有办法解决这个运行时问题?以下是我的头文件代码:

using namespace std; 
static map<pair<int, int>, double> map;

double val(int r, int b){   
if (0 == r)
    return ((double) b);
if (0 == b)
    return (0);
if (map.find(make_pair(r, b)) != map.end()){
    return (double)map[make_pair(r, b)];
} else{
    double num1 = ((double) r/(r+b)) * value(r-1, b);
    double num2 = ((double) b/(r+b)) * value(r, b-1);
    double value = max((num1 + num2), (double) (b - r));
    map[make_pair(r, b)] = value;
    return value;
}
}

#endif

在main()中用于打印消息:cout

【问题讨论】:

标签: c++ dictionary recursion data-structures memoization


【解决方案1】:

即使 totalNum 大于 99999,有没有办法解决这个运行时问题? 摆脱递归。很容易找出计算值(r, b) 所需的值。您可以使用循环来计算它们。

递归版本的问题是它需要大量的堆栈空间,这是相当有限的。因此,此解决方案无法扩展。记忆并没有改变这一点,因为函数参数和返回值仍然需要保存在堆栈上。即使可以优化递归(通常仅使用尾递归),未优化的构建仍然不会运行。这可能意味着您无法有效地调试您的程序。

当您更改为循环时,您可以将堆栈空间重新用于函数参数,并且可以将结果存储在堆上。

【讨论】:

  • The memoization doesn't change that, because function arguments and return value still need to be saved on the stack. 我认为这是一种误导。例如,假设函数 A 对其自身进行 10 次函数调用来计算输入 X,如果输入 X 已经与结果一起缓存,则记忆版本将需要大约 1 次函数调用。这可能会影响所使用的堆栈空间 - 与否,这取决于通常如何/何时调用函数 A。
  • @GabrielFrancischini 我的意思是记忆化并没有根本的区别;递归方法仍然无法扩展。相应地进行了编辑。
【解决方案2】:

优化(以及记忆化,作为一种优化技术)用于将慢代码变成更快的代码,而不是将容易出错的代码变成无错误的代码。

很可能您的段错误(参见 wiki 上的 common causes)与记忆化没有任何关系。当然,添加 memoization 会改变 segfault 的频率和行为,但这并没有导致它也没有删除它。

段错误的最常见原因是尝试使用已释放的(C++ 中的deleted)指针,但您的代码不直接处理任何原始指针,因此可能不是原因。

段错误的第二个最常见原因是返回对局部变量的引用,在这种情况下也不会发生这种情况。

但是,因为在记忆化之后,段错误发生在更大的数字(totalNum)上,它可能与堆栈大小有关。在 C++ 中,递归通常会导致堆栈使用量不断增加,并且您可以轻松地用完操作系统通常提供的所有堆栈(导致堆栈溢出,这通常表示为段错误)。这可能是分段错误的来源。如果您使用的是 linux,请尝试使用 ulimit -s 查看您当前的堆栈大小并粗暴地增加它(通过使用类似 ulimit -s 65536 的东西来获得 64 Mb 的堆栈),看看这是否会使您在更大的 totalNum 处出现段错误。如果是这样,那么您现在知道您的问题是堆栈溢出。 Then it will be easier to find solutions for it。因此,无错误的解决方案是将递归算法更改为基于任务的算法(即设置要处理的值队列,循环遍历它并处理其中的项目,而不是使用递归)。

否则,有更多代码可以查看,因为段错误不太可能是由这个记忆代码引起的。

此外,如果您提供一个最小的工作示例,其中包含一个虚拟的 main 函数和编译所需的一切,那将是非常好的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-04-28
    • 1970-01-01
    • 2014-12-05
    • 1970-01-01
    • 1970-01-01
    • 2022-01-23
    • 2012-05-05
    • 2013-05-04
    相关资源
    最近更新 更多