C++ 部分置换

C++ 部分置换,c++,permutation,C++,Permutation,我有以下用于输出部分组合的递归函数: void comb(string sofar, string rest, int n) { string substring; if (n == 0) cout << sofar << endl; else { for (size_t i = 0; i < rest.length(); i++) { substring = rest.substr

我有以下用于输出部分组合的递归函数:

void comb(string sofar, string rest, int n) {

    string substring;

    if (n == 0)
        cout << sofar << endl;
    else {
        for (size_t i = 0; i < rest.length(); i++) {
            substring = rest.substr(i + 1, rest.length());
            comb(sofar + rest[i], substring, n - 1);
        }
    }
}
通过部分组合,我的意思是它使用n个选项和r个元素(而不是n个选项,n个元素)


但是,我想考虑元素的顺序(即排列)。我可以找到许多用于完全置换的算法,但不是部分置换。

您需要置换,因此您应该在
子字符串中捕获
I
前后的字符:

substring = rest.substr(0, i) + rest.substr(i + 1, rest.length());
完成以下项目:


您可以使用标准库执行此操作:

// Requires: sequence from begin to end is sorted
//           middle is between begin and end
template<typename Bidi, typename Functor>
void for_each_permuted_combination(Bidi begin,
                                   Bidi middle,
                                   Bidi end,
                                   Functor func) {
  do {
    func(begin, middle);
    std::reverse(middle, end);
  } while (std::next_permutation(begin, end));
}
//要求:从开始到结束的顺序已排序
//中间在开始和结束之间
模板
每种排列组合无效(Bidi开始,
比迪中,
比迪端,
函子(func){
做{
func(开始,中间);
标准:反向(中间、末端);
}while(std::next_置换(begin,end));
}
这取决于
next_permutation
按字典顺序生成排列这一事实。因此,如果序列的尾部已排序,则反转尾部,然后请求整个序列的下一个排列,将头部移动到子集的下一个排列,并使尾部再次排序以供下一次迭代


在不明显的情况下,将使用一对表示置换组合范围的迭代器调用函子。

这里是性能真实性检查的时候了。如果你只想一次浏览5件3件的排列,那么现在就停止阅读,因为浏览的次数太少了,没什么大不了的(除非你已经浏览了10亿次)

但是,如果您需要访问更多的东西,一次访问更多的东西,那么性能将真正开始起作用。例如,如何访问26个字符串:“abcdefghijklmnopqrstuvxyz”,一次访问6个项目?随着排列,成本增长非常快

对于性能测试,最好将输出注释掉到终端,因为这往往是一个非常缓慢的操作,会淹没其他所有操作

(当前接受的)如下所示:

#include <chrono>
#include <iostream>
#include <string>

using std::string;
using std::cout;

void comb(string sofar, string rest, int n)
{
    // std::cout << "comb('" << sofar << "', '" << rest << "', " << n << ")\n";
    string substring;

    if (n == 0)
        ; //cout << sofar << '\n';
    else {
        for (size_t i = 0; i < rest.length(); i++) {
            substring = rest.substr(0, i) + rest.substr(i + 1, rest.length());
            comb(sofar + rest[i], substring, n - 1);
        }
    }
}

int main()
{
    std::string s("abcdefghijklmnopqrstuvwxyz");
    auto t0 = std::chrono::steady_clock::now();
    comb("",s, 6);
    auto t1 = std::chrono::steady_clock::now();
    std::cout << std::chrono::duration<double>{t1-t0}.count() << '\n';
}
速度要快得多

#include <algorithm>
#include <chrono>
#include <iostream>

// Requires: sequence from begin to end is sorted
//           middle is between begin and end
template<typename Bidi, typename Functor>
void for_each_permuted_combination(Bidi begin,
                                   Bidi middle,
                                   Bidi end,
                                   Functor func) {
  do {
    func(begin, middle);
    std::reverse(middle, end);
  } while (std::next_permutation(begin, end));
}

int
main()
{
    std::string s("abcdefghijklmnopqrstuvwxyz");
    auto t0 = std::chrono::steady_clock::now();
    for_each_permuted_combination(s.begin(), s.begin()+6, s.end(),
        [](auto first, auto last)
        {
//             for (; first != last; ++first)
//                 std::cout << *first;
//             std::cout << '\n';
        });
    auto t1 = std::chrono::steady_clock::now();
    std::cout << std::chrono::duration<double>{t1-t0}.count() << '\n';
}
这是69%的速度!这种速度的提高很大程度上可以归因于第一种算法中隐含的分配和解除分配的缺乏

然而,我想指出你

司机看起来像:

#include "combinations"
#include <chrono>
#include <iostream>
#include <string>

int
main()
{
    std::string s("abcdefghijklmnopqrstuvwxyz");
    auto t0 = std::chrono::steady_clock::now();
    for_each_permutation(s.begin(), s.begin()+6, s.end(),
        [](auto first, auto last)
        {
//             for (; first != last; ++first)
//                 std::cout << *first;
//             std::cout << '\n';
            return false;
        });
    auto t1 = std::chrono::steady_clock::now();
    std::cout << std::chrono::duration<double>{t1-t0}.count() << '\n';
}
这比快30倍,比快51倍!随着
n
r
的增长,这些性能数字的差异也在增长


它是免费的,开源的。实现位于链接处,可以从中复制/粘贴。我不会说这很简单。我只是说它的表现令人信服。性能上的差异是如此巨大,它可以决定在实际时间内解决问题与否。

您看过了吗?@AndyG感谢您的回答。我有,但似乎next_置换只能用于完全置换(n个选项,n个元素),而不是部分置换(n个选项,r个元素)。我明白了。我道歉。我想我现在明白了。你能详细解释一下“考虑元素的顺序”是什么意思吗?@TonyD谢谢你的建议。调出子字符串行似乎为5个选项、3个元素创建了25个排列,而我认为应该有60个。@AndyG正是这样。谢谢你的帮助!非常感谢你的帮助。这是完美的工作方式,所需的更改比我预期的要少得多@不客气。你很接近!干杯。谢谢你提供了非常翔实的答案。我确实遇到了性能问题,所以我将致力于在代码中实现您提到的最快算法。再次感谢!您好,Howard,69%的答案表现得非常奇怪:它似乎没有输出置换。此外,对于与OP相同的输入,它只在需要产生60个组合/置换时产生20个组合/置换。毫不奇怪,它比公认的答案快3倍。我一定是做错了什么,因为第二个答案(比答案快51倍)也表现得很奇怪,只为大小为3的输入生成了3个排列,而实际上是3!=6. : @AndyG:
s.begin()+1
要求排列一次选择1。如果要一次排列3件事情,请指定
s.begin()+3
。通常,对于一次要排列的长度为
n
的字符串
r
s.begin()+r
。建议中使用的确切内容是:code:
#include <chrono>
#include <iostream>
#include <string>

using std::string;
using std::cout;

void comb(string sofar, string rest, int n)
{
    // std::cout << "comb('" << sofar << "', '" << rest << "', " << n << ")\n";
    string substring;

    if (n == 0)
        ; //cout << sofar << '\n';
    else {
        for (size_t i = 0; i < rest.length(); i++) {
            substring = rest.substr(0, i) + rest.substr(i + 1, rest.length());
            comb(sofar + rest[i], substring, n - 1);
        }
    }
}

int main()
{
    std::string s("abcdefghijklmnopqrstuvwxyz");
    auto t0 = std::chrono::steady_clock::now();
    comb("",s, 6);
    auto t1 = std::chrono::steady_clock::now();
    std::cout << std::chrono::duration<double>{t1-t0}.count() << '\n';
}
14.2002
#include <algorithm>
#include <chrono>
#include <iostream>

// Requires: sequence from begin to end is sorted
//           middle is between begin and end
template<typename Bidi, typename Functor>
void for_each_permuted_combination(Bidi begin,
                                   Bidi middle,
                                   Bidi end,
                                   Functor func) {
  do {
    func(begin, middle);
    std::reverse(middle, end);
  } while (std::next_permutation(begin, end));
}

int
main()
{
    std::string s("abcdefghijklmnopqrstuvwxyz");
    auto t0 = std::chrono::steady_clock::now();
    for_each_permuted_combination(s.begin(), s.begin()+6, s.end(),
        [](auto first, auto last)
        {
//             for (; first != last; ++first)
//                 std::cout << *first;
//             std::cout << '\n';
        });
    auto t1 = std::chrono::steady_clock::now();
    std::cout << std::chrono::duration<double>{t1-t0}.count() << '\n';
}
8.39237
#include "combinations"
#include <chrono>
#include <iostream>
#include <string>

int
main()
{
    std::string s("abcdefghijklmnopqrstuvwxyz");
    auto t0 = std::chrono::steady_clock::now();
    for_each_permutation(s.begin(), s.begin()+6, s.end(),
        [](auto first, auto last)
        {
//             for (; first != last; ++first)
//                 std::cout << *first;
//             std::cout << '\n';
            return false;
        });
    auto t1 = std::chrono::steady_clock::now();
    std::cout << std::chrono::duration<double>{t1-t0}.count() << '\n';
}
0.2742