web-dev-qa-db-ja.com

C ++での順列と組み合わせのためのライブラリ関数

N個の要素からk個の要素のすべての組み合わせと順列を提供するためにC++で最も広く使用されている既存のライブラリは何ですか?

私はアルゴリズムではなく、既存のライブラリまたはメソッドを求めています。

ありがとう。

34
skydoor

組み合わせ:同じトピックに関するMark Nelsonの記事からnext_combination http://marknelson.us/2002/03/01/next-permutation 順列:STLからstd :: next_permutationがあります

   template <typename Iterator>
   inline bool next_combination(const Iterator first, Iterator k, const Iterator last)
   {
      if ((first == last) || (first == k) || (last == k))
         return false;
      Iterator itr1 = first;
      Iterator itr2 = last;
      ++itr1;
      if (last == itr1)
         return false;
      itr1 = last;
      --itr1;
      itr1 = k;
      --itr2;
      while (first != itr1)
      {
         if (*--itr1 < *itr2)
         {
            Iterator j = k;
            while (!(*itr1 < *j)) ++j;
            std::iter_swap(itr1,j);
            ++itr1;
            ++j;
            itr2 = k;
            std::rotate(itr1,j,last);
            while (last != j)
            {
               ++j;
               ++itr2;
            }
            std::rotate(k,itr2,last);
            return true;
         }
      }
      std::rotate(first,k,last);
      return false;
   }
19
Matthieu N.

ここで、dmanとCharlesBaileyによるソリューションをテストすることにしました。それらをそれぞれソリューションAおよびBと呼びます。私のテストでは、vector<int>サイズ= 100、5の各組み合わせを一度に訪問しています。テストコードは次のとおりです。

テストコード

struct F
{
    unsigned long long count_;

    F() : count_(0) {}

    bool operator()(std::vector<int>::iterator, std::vector<int>::iterator)
    {++count_; return false;}
};

int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double> sec;
    typedef std::chrono::duration<double, std::nano> ns;
    int n = 100;
    std::vector<int> v(n);
    std::iota(v.begin(), v.end(), 0);
    std::vector<int>::iterator r = v.begin() + 5;
    F f;
    Clock::time_point t0 = Clock::now();
    do
    {
        f(v.begin(), r);
    } while (next_combination(v.begin(), r, v.end()));
    Clock::time_point t1 = Clock::now();
    sec s0 = t1 - t0;
    ns pvt0 = s0 / f.count_;
    std::cout << "N = " << v.size() << ", r = " << r-v.begin()
              << ", visits = " << f.count_ << '\n'
              << "\tnext_combination total = " << s0.count() << " seconds\n"
              << "\tnext_combination per visit = " << pvt0.count() << " ns";
}

すべてのコードは、2.8 GHz Intel Corei5でclang ++-O3を使用してコンパイルされました。

ソリューションA

解決策Aは、無限ループになります。 nを非常に小さくしても、このプログラムは完了しません。その後、この理由で反対票を投じました。

ソリューションB

これは編集です。この回答を書く過程で解決策Bが変更されました。最初は間違った答えを出していましたが、非常に迅速な更新により、今では正しい答えを出します。印刷します:

N = 100, r = 5, visits = 75287520
    next_combination total = 4519.84 seconds
    next_combination per visit = 60034.3 ns

ソリューションC

次に、 N2639 のソリューションを試しました。これは、ソリューションAと非常によく似ていますが、正しく機能します。このソリューションをCと呼び、次のように出力します。

N = 100, r = 5, visits = 75287520
    next_combination total = 6.42602 seconds
    next_combination per visit = 85.3531 ns

ソリューションCは、ソリューションBよりも703倍高速です。

ソリューションD

最後に、見つかった解Dがあります ここ 。このソリューションは異なる署名/スタイルを持ち、for_each_combinationと呼ばれ、std::for_eachのように使用されます。上記のドライバーコードは、タイマー呼び出し間で次のように変化します。

Clock::time_point t0 = Clock::now();
f = for_each_combination(v.begin(), r, v.end(), f);
Clock::time_point t1 = Clock::now();

ソリューションDは次のように出力します。

N = 100, r = 5, visits = 75287520
    for_each_combination = 0.498979 seconds
    for_each_combination per visit = 6.62765 ns

ソリューションDはソリューションCより12.9倍高速で、ソリューションBより9000倍以上高速です。

これは比較的小さな問題だと思います。訪問数はわずか7500万です。訪問数が数十億に増加するにつれて、これらのアルゴリズム間のパフォーマンスの不一致は拡大し続けます。ソリューションBはすでに扱いにくいです。ソリューションCは最終的に扱いにくくなります。ソリューションDは、私が知っているすべての組み合わせにアクセスするための最高のパフォーマンスのアルゴリズムです。

ソリューションDを示すリンク には、さまざまなプロパティ(循環、可逆など)を持つ順列を列挙して訪問するための他のいくつかのアルゴリズムも含まれています。これらの各アルゴリズムは、パフォーマンスを目標の1つとして設計されています。また、これらのアルゴリズムのいずれも、最初のシーケンスがソートされた順序である必要がないことに注意してください。要素はLessThanComparableである必要はありません。

38
Howard Hinnant

この回答は、最小限の実装作業ソリューションを提供します。大きな入力範囲の組み合わせを取得する場合は、許容できるパフォーマンスが得られない可能性があります。

標準ライブラリにはstd::next_permutationがあり、そこからnext_k_permutationを作成し、そこからnext_combinationを簡単に作成できます。

template<class RandIt, class Compare>
bool next_k_permutation(RandIt first, RandIt mid, RandIt last, Compare comp)
{
    std::sort(mid, last, std::tr1::bind(comp, std::tr1::placeholders::_2
                                            , std::tr1::placeholders::_1));
    return std::next_permutation(first, last, comp);
}

tr1::bindまたはboost::bindがない場合は、引数を特定の比較に交換する関数オブジェクトを作成する必要があります。もちろん、std::lessnext_combinationバリアントのみに関心がある場合は、std::greaterを直接使用できます。

template<class RandIt>
bool next_k_permutation(RandIt first, RandIt mid, RandIt last)
{
    typedef typename std::iterator_traits<RandIt>::value_type value_type;

    std::sort(mid, last, std::greater< value_type >());
    return std::next_permutation(first, last);
}

これはnext_combinationの比較的安全なバージョンです。範囲[mid, last)next_combinationの呼び出し後と同じように正しいことを保証できる場合は、より単純なものを使用できます。

template<class BiDiIt, class Compare>
bool next_k_permutation(BiDiIt first, BiDiIt mid, BiDiIt last, Compare comp)
{
    std::reverse(mid, last);
    return std::next_permutation(first, last, comp);
}

これは、双方向イテレータおよびランダムアクセスイテレータでも機能します。

K順列の代わりに組み合わせを出力するには、各組み合わせを1回だけ出力するようにする必要があるため、順番にk順列である場合にのみ組み合わせを返します。

template<class BiDiIt, class Compare>
bool next_combination(BiDiIt first, BiDiIt mid, BiDiIt last, Compare comp)
{
    bool result;
    do
    {
        result = next_k_permutation(first, mid, last, comp);
    } while (std::adjacent_find( first, mid,
                             std::tr1::bind(comp, std::tr1::placeholders::_2
                                                , std::tr1::placeholders::_1) )
                                                                        != mid );
    return result;
}

別の方法は、パラメータスワッピングbind呼び出しの代わりに逆イテレータを使用するか、std::greaterが使用されている比較である場合は、std::lessを明示的に使用することです。

17
CB Bailey

上記の@チャールズベイリー:

私は間違っている可能性がありますが、上記の最初の2つのアルゴリズムは、最初と中間の間の重複を削除しないと思いますか?使い方がわからないかもしれません。

42つの例を選択します。
12 34
12 43(ソート後)
1 24(next_permutationの後)
1 42(ソート後)
14 23(next_permutationの後)
14 32(ソート後)
21 34(next_permutationの後)

そこで、戻る前にイタリック体の値が正しいかどうかを確認するチェックを追加しましたが、あなたが書いた部分については間違いなく考えていなかったでしょう(非常にエレガントです!ありがとう!)。

完全にはテストされていません。大まかなテストだけです。


template
bool next_combination(RandIt first, RandIt mid, RandIt last)
{
    typedef typename std::iterator_traits< RandIt >::value_type value_type;
    std::sort(mid, last, std::greater< value_type >() );
    while(std::next_permutation(first, last)){
        if(std::adjacent_find(first, mid, std::greater< value_type >() ) == mid){
            return true;
        }
        std::sort(mid, last, std::greater< value_type >() );
    return false;
}
2
AWU