(これは完全な答えではありません。問題の一部、つまりビット操作に関する部分のみを解決します。)
2 つのセット間の交差の数 (交差のカーディナリティ) をかなり迅速に計算するために使用できるクラスを次に示します。
ビット ベクトルを使用してセットを格納します。つまり、可能なセット メンバーのユニバースは小さくなければなりません。
#include <cassert>
#include <vector>
class BitVector
{
// IMPORTANT: U must be unsigned
// IMPORTANT: use unsigned long long in 64-bit builds.
typedef unsigned long U;
static const unsigned UBits = 8 * sizeof(U);
public:
BitVector (unsigned size)
: m_bits ((size + UBits - 1) / UBits, 0)
, m_size (size)
{
}
void set (unsigned bit)
{
assert (bit < m_size);
m_bits[bit / UBits] |= (U)1 << (bit % UBits);
}
void clear (unsigned bit)
{
assert (bit < m_size);
m_bits[bit / UBits] &= ~((U)1 << (bit % UBits));
}
unsigned countIntersect (BitVector const & that) const
{
assert (m_size == that.m_size);
unsigned ret = 0;
for (std::vector<U>::const_iterator i = m_bits.cbegin(),
j = that.m_bits.cbegin(), e = m_bits.cend(), f = that.m_bits.cend();
i != e && j != f; ++i, ++j)
{
U x = *i & *j;
// Count the number of 1 bits in x and add it to ret
// There are much better ways than this,
// e.g. using the POPCNT instruction or intrinsic
while (x != 0)
{
ret += x & 1;
x >>= 1;
}
}
return ret;
}
unsigned countUnion (BitVector const & that) const
{
assert (m_size == that.m_size);
unsigned ret = 0;
for (std::vector<U>::const_iterator i = m_bits.cbegin(),
j = that.m_bits.cbegin(), e = m_bits.cend(), f = that.m_bits.cend();
i != e && j != f; ++i, ++j)
{
U x = *i | *j;
while (x != 0)
{
ret += x & 1;
x >>= 1;
}
}
return ret;
}
private:
std::vector<U> m_bits;
unsigned m_size;
};
上記のクラスをどのように使用できるかを確認するための非常に小さなテスト プログラムを次に示します。2 つのセット (それぞれ最大 100K の要素を持つ) を作成し、いくつかの項目を (set()
メンバー関数を使用して) それらに追加し、それらの交差を 10 億回計算します。私のマシンでは 2 秒以内に実行されます。
#include <iostream>
using namespace std;
int main ()
{
unsigned const SetSize = 100000;
BitVector a (SetSize), b (SetSize);
for (unsigned i = 0; i < SetSize; i += 2) a.set (i);
for (unsigned i = 0; i < SetSize; i += 3) b.set (i);
unsigned x = a.countIntersect (b);
cout << x << endl;
return 0;
}
これを最適化を有効にしてコンパイルすることを忘れないでください! そうしないと、パフォーマンスが非常に悪くなります。
POPCNT
最新のプロセッサには、POPCNT と呼ばれる、ワードに設定されたビット数をカウントする命令があります。これは、上記の単純なことを実行するよりもはるかに高速です (補足として、ソフトウェアで実行するより高速な方法もありますが、コードを汚染したくありませんでした。)
とにかく、C/C++ コードで POPCNT を使用する方法は、コンパイラの組み込み関数または組み込み関数を使用することです。__popcount()
MSVC では、 32 ビット整数で機能する組み込み関数を使用できます。__builtin_popcountl()
GCC では、 32 ビット整数と__builtin_popcountll()
64 ビットに使用できます。これらの関数は、コンパイラのバージョン、ターゲット アーキテクチャ、および/またはコンパイル スイッチが原因で使用できない場合があることに注意してください。