【问题标题】:Why does this solution works in JavaScript but takes too long in C++? (Dynamic Programming)为什么这个解决方案在 JavaScript 中有效,但在 C++ 中耗时太长? (动态编程)
【发布时间】:2021-06-17 14:17:38
【问题描述】:

这是我的canSum 函数需要做的:

给定目标总和x,返回true,如果可以通过添加给定数组中的元素来获得该总和,假设数组元素可以使用任意次数。

例子:

canSum(7, {2,3}) -> true
canSum(7, {2,4}) -> false

下面是我用 C++ 重写的 JavaScript 代码。出于某种原因,即使我使用了 memoization,C++ 版本对于大输入也需要很长时间。

JavaScript 代码,运行良好:

const canSum = (targetSum, numbers, memo={}) => {
    if (targetSum === 0) return true;
    if (targetSum < 0) return false;
    for ( let num of numbers) {
        const remainder = targetSum - num;
        if (canSum( remainder, numbers, memo) === true) {
            return true;
        }
    }
    return false;
};


console.log(canSum(7, [2, 3])); // true
console.log(canSum(7, [5, 3, 4, 7])); // true
console.log(canSum(7, [2, 41])); // false
console.log(canSum(8, [2, 3, 5])); // true
console.log(canSum(300, [7, 14])); // false

我的 C++ 代码,它从未为最后一个输入项提供任何输出 canSum(300, {7,14})

#include<bits/stdc++.h>
using namespace std;

unordered_map<int,bool> mymap;

bool canSum(int goal, vector<int> vec)
{
    if (goal<0)       return false;
    if (goal==0)      return true;
    if (mymap[goal])  return mymap[goal];

    for(auto&ell:vec)
    {
        if(canSum(goal-ell,vec)==true) 
        {
            mymap.insert({goal,true});
            return true;
        }
    }
    mymap.insert({goal,false});
    return false;
}

int main()
{
    cout<<canSum(7, {2,3})<<endl;
    cout<<canSum(7, {5,3,4,7})<<endl;
    cout<<canSum(7, {2,4})<<endl;
    cout<<canSum(8, {2,3,5})<<endl;
    cout<<canSum(300, {7,14})<<endl;

    return 0;
}

如何优化 C++ 代码,为什么 JavaScript 代码更快?

【问题讨论】:

  • 为什么js代码比较快? -- C++代码慢的证据在哪里?询问“为什么这么慢?”的问题将随附所使用的编译器、用于构建应用程序的编译器选项、是否在打开优化的情况下构建,以及具有测试和输出此信息的计时功能的实际 C++ 代码(或代码在在线性能测试员)。
  • javascript 可能通过引用传递对象,而 C++ 传递一个副本,除非你告诉它不同。也许试试bool canSum(int goal, vector&lt;int&gt; const&amp; vec)?也使用优化进行编译。
  • if (mymap[goal]) return mymap[goal]; -- 你在这里的意图是什么?如果是搜索地图看goal是否已经在地图中,那行代码不会这样做。一个从另一种语言逐行翻译到 C++ 的经典案例,然后失败了。
  • 请注意,在 C++ 中使用[ ] 访问地图将自动插入一个条目,如果该条目不在地图中。另一个问题 - 您可能会通过在该行上使用 [ ] 两次来进行两次查找。基本上,需要重写那行代码才能进行一次实际的搜索一次,并根据值是否存在,用该值做一些事情。 unordered_map 有一个你应该使用的find() 方法,而不是盲目地使用[ ]。 find() 将返回一个指向现有项目的迭代器,如果项目不存在,则返回 end()。
  • 您似乎没有在两次调用 canSum() 之间重置您的 global 映射对象。

标签: javascript c++ dynamic-programming memoization


【解决方案1】:

您的 JavaScript 和 C++ 代码示例之间的区别在于:C++ 函数只有两个参数而不是 3 个参数,地图对象作为全局实体进行管理。

在某种意义上,只有 2 个参数是“The Right Thing”。无序映射是关于算法的一些内部必要性。为什么用户代码必须知道这一点?

如果有一天你决定使用 ordered 映射或集合或位向量,为什么要强制用户代码将其必须的头文件列表更改为 #include ?

所以只有 2 个参数就可以了,但是将地图作为外部 全局对象 管理并不是那么好。在 C++ 编程中,全局对象通常不受欢迎。在您的情况下,它给用户代码带来了过度的负担,例如必须在两次调用 canSum() 之间重置地图对象?这样的预防措施太容易忘记了。

要解决这个问题,您可以使用两个 C++ 函数:一个外部函数,一个内部函数。

外部负责(内部)地图对象的生命周期。内部的只是传递一个指向地图对象的指针。

内部函数的C++代码:

#include  <vector>
#include  <unordered_map>
#include  <iostream>

using  MyMapType = std::unordered_map<int, bool>;  // ad hoc map type
using  std::vector;

bool canSumWithMap(int goal, const vector<int>& vec, MyMapType& myMap)
{
    if (goal < 0)   return false;
    if (goal == 0)  return true;

    if (myMap[goal])
        return myMap[goal];

    for (auto& ell : vec)
    {
        if (canSumWithMap(goal-ell, vec, myMap)) 
        {
            myMap.insert({goal, true});
            return true;
        }
    }
    myMap.insert({goal, false});
    return false;
}

请注意,地图和矢量都是通过引用传递的,并带有'&'字符,以避免在函数调用期间进行不必要的复制。

外部函数的C++代码,加上主程序:

bool canSum(int goal, const vector<int>& vec)
{
    MyMapType  myMap;  // new map object initialized as empty

    bool rc = canSumWithMap(goal, vec, myMap);

    return rc;
    // myMap automagically deallocated here
}


using  std::cout;
using  std::endl;


int main()
{
    cout << std::boolalpha;  // want to print true or false rather than 0 or 1
    cout << canSum(7, {2,3})      << endl;
    cout << canSum(7, {5,3,4,7})  << endl;
    cout << canSum(7, {2,4})      << endl;  // no, can only do multiples of 2
    cout << canSum(8, {2,3,5})    << endl;
    cout << canSum(300, {7,14})   << endl;  // no, can only do multiples of 7

    return 0;
}

上面的代码在我的半老式 Intel x86-64 机器上成功运行 50 秒,GNU C++ v10.2,带有 -O2 选项。

程序输出:

$ g++ --version
g++ (GCC) 10.2.1 20201125 (Red Hat 10.2.1-9)
Copyright © 2020 Free Software Foundation, Inc.
  ...  
$ 
$ g++ -O2 q66720598.cpp  -o q66720598.x
$ time q66720598.x
true
true
false
true
false

real   0m49,986s
user   0m49,841s
sys    0m0,003s
$ 

性能测量:

在我的机器上,您的 JavaScript 代码在 19 秒内运行。而 C++ 的无序映射需要 50 秒,这有点令人失望。

从无序映射切换到普通(有序)映射将 C++ 时间减少到 36 秒,这仍然比 JavaScript 慢。

它需要一个位向量,定义如下:

    std::vector<bool>  myMap(goal+1, false);

恢复适当的层次结构 :-) 并使 C++ 比 JavaScript 快 3 倍,而挂墙时间仅为 6 秒。

因此,这是其中一种情况,地图对象虽然功能强大且用途广泛,但可能比某些 ad hoc 数据结构慢得多。

【讨论】:

  • 非常感谢您花时间如此深入地解释这一点!真的很感激!!
猜你喜欢
  • 2021-10-01
  • 1970-01-01
  • 2017-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-20
  • 1970-01-01
相关资源
最近更新 更多