【问题标题】:Optimize C++ function to generate combinations优化C++函数生成组合
【发布时间】:2020-04-05 08:03:51
【问题描述】:

我正在尝试获取一个函数来生成所有可能的数字组合,但我的问题是处理时间太长。所以我认为我必须优化它。 问题:生成所有具有 1 到 n 个元素的“r”大小集合,而不以相反的顺序重复(1,2 等于 2, 1)。

例子:

n = 3 //elements: 1,2,3
r = 2 //size of set

Output:
2 3
1 3
1 2

我使用的代码如下:

    void func(int n, int r){
        vector <vector <int>> reas;
        vector<bool> v(n);
        fill(v.end() - r, v.end(), true);

        int a = 0;
        do {
            reas.emplace_back();
            for (int i = 0; i < n; ++i) {
                if (v[i]) {
                    reas[a].push_back(i+1);
                }
            }
            a++;
        } while (next_permutation(v.begin(), v.end()));
    }

如果 n = 3 和 r = 2,输出将与上例相同。 我的问题是,如果我设置 n = 50 和 r = 5,则精化时间太长,我需要使用 n = 50...100 和 r= 1..5 的范围; 有没有办法优化这个功能?

非常感谢

【问题讨论】:

    标签: c++ math optimization combinations


    【解决方案1】:

    是的,有几件事可以显着改进。但是,您应该记住,您正在计算的组合数量非常大,如果要枚举所有子集,它必须很慢。在我的机器上和我个人的耐心预算(100,5) 是遥不可及的。

    鉴于此,您可以在不完全重写整个算法的情况下改进以下内容。


    第一:缓存位置

    vector&lt;vector&lt;T&gt;&gt; 不会是连续的。嵌套向量相当小,所以即使使用预分配,这总是很糟糕,并且迭代它会很慢,因为每个新的子向量(并且有 很多)可能会导致缓存未命中.

    因此,请使用单个 vector&lt;T&gt;。然后,您的kth 子集将不会位于位置k,而是位于k*r。但这对我的机器来说是一个显着的加速。

    第二:使用对 cpu 友好的置换向量

    您使用next_permutation 的想法不错。但是您使用vector&lt;bool&gt; 的事实使得这非常慢。矛盾的是,使用vector&lt;size_t&gt;快得多,因为加载size_t 并检查它比使用bool 更容易。

    所以,如果你把它们放在一起,代码看起来像这样:

      auto func2(std::size_t n, std::size_t r){
        std::vector<std::size_t> reas;
        reas.reserve((1<<r)*n);
        std::vector<std::size_t> v(n);
        std::fill(v.end() - r, v.end(), 1); 
    
        do {
          for (std::size_t i = 0; i < n; ++i) {
            if (v[i]) {
              reas.push_back(i+1);
            }
          }   
        } while (std::next_permutation(v.begin(), v.end()));
        return reas;
      }
    

    第三:不要将整个结果压入一个巨大的缓冲区中

    使用回调来处理每个子集。从而避免返回一个巨大的向量。相反,您为找到的每个单独的子集调用一个函数。如果你真的需要一个巨大的集合,这个回调仍然可以将子集合推入一个向量,但它也可以就地操作它们。

      std::size_t func3(std::size_t n, std::size_t r, 
                        std::function<void(std::vector<std::size_t> const&)> fun){
        std::vector<std::size_t> reas;
        reas.reserve(r);
        std::vector<std::size_t> v(n);
        std::fill(v.end() - r, v.end(), 1);
    
        std::size_t num = 0;
        do {
          reas.clear(); // does not shrink capacity to 0
          for (std::size_t i = 0; i < n; ++i) {
            if (v[i]) {
              reas.push_back(i+1);
            }
          }
          ++num;
          fun(reas);
        } while (std::next_permutation(v.begin(), v.end()));
        return num;
      }
    

    这在我的实验中产生了超过 2 倍的加速。但是,当你加速 nr 时,加速会提高。

    另外:使用编译器优化

    使用您的编译器选项尽可能加快编译速度。在我的系统上,从 -O0 到 -O1 的跳跃是超过 10 倍的加速。从 -O1 到 -O3 的跳跃要小得多,但仍然存在(大约 x1.1)。


    与性能无关,但仍然相关:Why is "using namespace std;" considered bad practice?

    【讨论】:

      猜你喜欢
      • 2018-02-24
      • 1970-01-01
      • 2021-12-17
      • 1970-01-01
      • 2018-01-13
      • 2015-05-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多