【问题标题】:Parallel code for next_permutation()next_permutation() 的并行代码
【发布时间】:2015-09-01 03:11:26
【问题描述】:

我想知道是否可以使用 OpenMP 并行化此代码。 OpenMP 会让代码运行得更快吗?有没有更好的方法来实现这一点?

vector<int> t; // already initialized
do{
    // Something with current permutation

} while(next_permutation(t.begin(), t.end()));

我知道我可以轻松并行化 for 指令,但这里我有一个 while (condition = true)。

【问题讨论】:

    标签: c++ algorithm optimization openmp permutation


    【解决方案1】:

    使用Finding n-th permutation without computing others得到第k个排列,对于k=i*n/count,i从0到count,其中n是排列的数量,i是一个索引,@987654328 @ 是线程数。

    这为您提供count 块或间隔。在单独的线程中在每个时间间隔内并行迭代,在每个线程中重复调用next_permutation。

    【讨论】:

    • 这实际上是一种非常缓慢的迭代排列方式,因为它每一步都使用 n 个除法。
    • @rici 它提供对排列的随机访问,您不使用它进行迭代。你用它来跳转到 0n/c, 1n/c, 2n/c, ..., (c-1)n/c。然后每个线程使用下一个排列,从上述起点之一开始。我只需要比 n/c 进步更快。
    • @ricin 指的是排列的数量,而不是元素的数量。当我们谈论排列的随机访问时,这似乎很自然。
    【解决方案2】:
    1. next_permutation 按字典顺序产生排列,这意味着产生的排列的前缀也是按字典顺序排列的。换句话说,您可以通过单独处理每个可能的初始元素来进行非常粗略的并行化:

      // Assume that v is sorted (or sort it)
      // This `for` loop should be parallelized
      for (auto n = v.size(), i = 0; i < n; ++i) {
        // Make a copy of v with the element at 'it' rotated to the beginning
        auto vprime = v;
        std::rotate(vprime.begin(), vprime.begin() + i, vprime.begin() + i + 1);
        // The above guarantees that vprime[1:] is still sorted.
        // Since vprime[0] is constant, we only need to permute vprime[1:]
        while (std::next_permutation(vprime.begin() + 1, vprime.end()) {
          // do something with vprime
        }
      }
      

      以上假设每个排列的处理时间大致相同。如果某个初始元素的排列的处理时间与其他一些初始元素的排列的平均时间不同,那么一些线程将在其他线程之前终止,从而降低并行化的有效性。您可以通过使用大于一个元素的前缀来缩小并行化块。

    2. 看来您真正想要的是从 n 元素的向量中生成所有 k 元素集的排列。有n!/(n-k)!这样的排列,很快就变成了一个非常大的数字。例如,如果 n 为 15,k 为 10,则有 10,897,286,400 个排列。处理所有这些都需要一段时间,即使处理速度非常快。所以你寻求一种并行工作的方法是正确的。

    3. 要找到 k 组合的所有排列,对每个可能的组合执行 next_permutation 是一种合理的方法,如果您有一个库函数可以生成所有组合。但请注意,next_combination 的许多实现都针对易用性而非性能进行了优化。在循环中有效地执行next_combination 需要一个持久状态,这可以从根本上降低搜索下一个组合的成本。

      另一种方法是使用next_partial_permutation 的实现,它直接产生n 个元素中k 个的下一个排列。一个简单的解决方案是基于next_permutation,但这也是次优的,因为额外调用了std::reverse。 (值得思考为什么这个算法有效。一个提示:如果你反转一个序列的字典顺序排列,你会得到字典顺序排列的最后一个排列。)(代码改编自 N2639)

      template<typename BidiIt>
      bool next_partial_permutation(BidiIt first, BidiIt middle, BidiIt last) {
        std::reverse(middle, last);
        return std::next_permutation(first, last);
      }
      

      无论您如何计算部分排列,您都可以使用与上述相同的方法对算法进行并行化:按前缀(或初始元素,如果处理时间不变)对排列进行分块,然后并行执行这些块。

    【讨论】:

    • 谢谢你的帮助,你明白我在做什么:找到 k 组合的所有排列。看来我将不得不处理 n=15 和 k=10 ...我会尝试您的解决方案 :) 但您似乎对这个主题了解很多,所以您如何看待我在这里找到的 howardhinnant.github.io/combinations.html ,这比您提出的解决方案更快吗?
    • @TerryBlain:它独立于我提出的解决方案。您需要对排列进行分片,以便可以并行执行它们。在每个分片中,我认为 Howard Hinnant 的解决方案可能是最快的,但最重要的可能是处理排列的速度。
    • 所以您建议我同时使用您的解决方案和 Howard Hinnant 的解决方案,以便一方面获得并行化,并且以最快的方式处理每个结果,对吗? (对不起我的英语..)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多