記事ディレクトリ
ビットマップ
ソートされていない 40 億個の一意の符号なし整数が与えられます。符号なし整数が与えられた場合、その数値が 40 億の数値の中にあるかどうかを迅速に判断するにはどうすればよいでしょうか?
真ん中。【テンセント】
- 走査、時間計算量 O(N)
- ソート (O(NlogN))、二分検索を使用: logN
512M のメモリを申請する
ビットは unsigned int 値を表します
40億の数値を読み取り、対応するビットを設定します
問い合わせたい数値を読み込み、対応するビットが1かどうかを確認します。1であれば存在し、0であれば存在しません。
各バイナリ ビットを使用して 1 つのデータを記録できます。int は 1 つのデータしか記録できませんでしたが、64 個の数値を記録できるようになり、スペースが大幅に削減されました。
リトル エンディアン ストレージを備えたマシンでは、データは次のように格納されます。データの上位バイトはメモリの上位アドレスに格納され、データの下位バイトは下位アドレスに格納されます。
たとえば、19を記録したいとします。
19/8=2
19%8=3
0 から数えると、元は 19/8=2 なので、3 バイト目の 3 番目の位置になります (位置は 0 から数えます)。
データの追加方法
データ6 をビットマップに追加したい場合、初期化されたビットマップのステータスはすべて 0 です。
以下の図に示すように (ビットマップの初期状態):
0 0 0 0 0 0 0 0 6/8=0;
6%8=6;
つまり、0 バイト目の 6 番目の位置になります。
次のようなバイナリ シーケンスを構築できます。
0 1 0 0 0 0 0 0 2 つのバイナリ シーケンス 0|1=1、0|0=0 のビット単位の OR (|) を計算します。これにより、他の位置のデータは変更されません。
では、どのように構築すればよいのでしょうか?
1 を左 (<<) j 位置に移動するだけです
1<<j
。
データの削除方法
ビットマップからデータ6を削除したい場合
6/8=0;
6%8=6;
つまり、0 バイト目の 6 番目の位置になります。
以下の図に示すように (ビットマップの初期状態):
0 1 0 0 0 0 0 0 次のようなバイナリ シーケンスを構築できます。
1 0 1 1 1 1 1 1 ビット単位の AND (&) ** 2 つのバイナリ シーケンス、0&1=0、0&0=0、1&1=1 は、他の位置のデータを変更しません。
では、どのように構築すればよいのでしょうか?
1 を j 位置だけ左にシフトし (<<)、それをビット単位で反転します
~(1<<j)
。
コード
namespace mudan
{
template<size_t N>
class bitset
{
public:
bitset()
{
_bits.resize(N / 8 + 1, 0);//计算出需要多少字节来存储
}
void set(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
_bits[i] |= (1 << j);
}
void reset(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
_bits[i] &= ~ (1 << j);
}
bool test(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
return _bits[i] & (1 << j);
}
private:
vector<char> _bits;
};
void test_bit_set1()
{
bitset<100> bs1;
bs1.set(8);
bs1.set(9);
bs1.set(20);
cout << bs1.test(8) << endl;
cout << bs1.test(9) << endl;
cout << bs1.test(20) << endl;
cout <<"=====================" << endl;
bs1.reset(8);
bs1.reset(9);
bs1.reset(20);
cout << bs1.test(8) << endl;
cout << bs1.test(9) << endl;
cout << bs1.test(20) << endl;
}
}
テストの結果、正常に動作していることがわかり、この番号が見つかりましたが、削除した後は見つかりません。
100 億個の整数がある場合、1 回だけ現れる数値を見つけるにはどうすればよいでしょうか?
2 つのビットマップを使用して状況を記録できます
分類:
0回出現:00
1回出現:01
1回以上:10回
コード
template<size_t N>
class twobitset
{
public:
void set(size_t x)
{
bool inset1 = _bs1.test(x);
bool inset2 = _bs2.test(x);
//00的情况
if (inset1 == false && inset2 == false)
{
//变成01,出现一次
_bs2.set(x);
}
else if (inset1 == false && inset2 == true)
{
//出现了一次,变成一次以上
_bs1.set(x);//1
_bs2.reset(x);//0
}
else if (inset1 == true && inset2 == false)
{
//10->11
_bs1.set(x);
_bs2.set(x);
}
}
void print_once_num()
{
for (size_t i = 0; i < N; ++i)
{
if (_bs1.test(i) == false && _bs2.test(i) == true)
{
cout << i << endl;
}
}
}
private:
bitset<N> _bs1;
bitset<N> _bs2;
};
void test_bit_set3()
{
int a[] = {
3, 4, 5, 2, 3, 4, 4, 4, 4, 12, 77, 65, 44, 4, 44, 99, 33, 33, 33, 6, 5, 34, 12 };
twobitset<100> bs;
for (auto e : a)
{
bs.set(e);
}
bs.print_once_num();
}
ご覧のとおり、1 回だけ出現する数値が除外されています。
類推すると、この方法は 2 回、3 回、4 回、5 回、6 回使用できます。
それぞれ 100 億の整数を含む 2 つのファイルがあるが、メモリが 1g しかない場合、ファイルの共通部分を見つけるにはどうすればよいでしょうか?
これは実際には単純です。2 つのビットマップを開いて、ビットマップを走査します。1 1 は交差があることを意味し、そうでない場合は交差はありません。
1 つのファイルには 100 億の整数と 1G のメモリがあり、2 回以内に出現するすべての整数を見つけるアルゴリズムを設計します。
前のソリューションと同様に、いくつかの状態を記録します
0回:00
1回:01
2回:10回
3回以上:11回