部分排列

时间:2016-01-19 01:11:02

标签: 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(i + 1, rest.length());
            comb(sofar + rest[i], substring, n - 1);
        }
    }
}

这样称呼:

comb("", "abcde", 3);

通过部分组合,我的意思是它使用n个选项和r个元素(而不是n个选项,n个元素)。

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

3 个答案:

答案 0 :(得分:11)

现在是时候进行性能现实检查了。如果你只想一次访问5件事的排列3,那么现在就停止阅读,因为访问次数太小而且没有多大关系(除非你这样做十亿次)。

但如果你需要一次访问更多的东西和更多的东西,性能真的开始变得重要。例如,访问字符串26:“abcdefghijklmnopqrstuvwxyz”,一次六个项目?通过排列,成本增长非常快......

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

This answer(目前接受的一个)看起来像这样:

#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';
}

在我的系统(clang++ -std=c++14 test.cpp -O3)上输出:

14.2002

This answer using std::next_permutation要快得多。

#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

这快69%!很多这种速度增加可归因于缺少第一种算法中隐含的分配和释放。

但我想指出an even faster algorithm

驱动程序如下:

#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

the answer using std::next_permutation 快30倍 the currently accepted answer快51倍!随着nr的增长,这些效果数字的差异也会增大。

linked library是免费且开源的。实现位于链接处,可以从中复制/粘贴。我不会说它很简单。我只声称其性能引人注目。性能上的差异是如此显着,以至于它可以在实际的时间内解决问题之间的区别。

答案 1 :(得分:4)

您想要排列,因此您应该在i substring之后的substring = rest.substr(0, i) + rest.substr(i + 1, rest.length()); 之后捕获之前的字符

#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()
{
    comb("", "abcde", 3);
}

coliru 上的完整程序:

abc abd abe acb acd ace adb adc ade aeb aec aed
bac bad bae bca bcd bce bda bdc bde bea bec bed
cab cad cae cba cbd cbe cda cdb cde cea ceb ced
dab dac dae dba dbc dbe dca dcb dce dea deb dec
eab eac ead eba ebc ebd eca ecb ecd eda edb edc 

输出(重新格式化):

Process

答案 2 :(得分:1)

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

// 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));
}

这取决于next_permutation按字典顺序生成排列的事实。因此,如果序列的尾部被排序,则反转尾部然后要求整个序列的下一个排列将头部移动到子集的下一个排列,并且再次为下一次迭代排序尾部。

如果它不明显,则使用一对迭代器调用仿函数,迭代器表示置换组合的范围。