N個の要素からk個の要素のすべての組み合わせと順列を提供するためにC++で最も広く使用されている既存のライブラリは何ですか?
私はアルゴリズムではなく、既存のライブラリまたはメソッドを求めています。
ありがとう。
組み合わせ:同じトピックに関する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;
}
ここで、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
である必要はありません。
この回答は、最小限の実装作業ソリューションを提供します。大きな入力範囲の組み合わせを取得する場合は、許容できるパフォーマンスが得られない可能性があります。
標準ライブラリには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::less
のnext_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
を明示的に使用することです。
上記の@チャールズベイリー:
私は間違っている可能性がありますが、上記の最初の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;
}