[データ構造] ハッシュアプリケーション

目次

1.ビットマップ

1. ビットマップの概念

2. ビットマップの実装

2.1、ビットマップ構造

2.2、ビット位置 1

2.3、ビット位置 0

2.4. ビットマップ内のビットの検出

3. ビットマップの例

3.1. 1 回だけ出現する整数を検索する

3.2、2 つのファイルの共通部分を見つける

3.3. 2 回以内に出現するすべての整数を検索する

2. ブルームフィルター

1. ブルームフィルターの提案

2. ブルームフィルターの概念

3. ブルームフィルターの実装

3.1、ブルームフィルターの挿入

3.2、ブルームフィルター検索

3.3、ブルームフィルターの削除

4. ブルームフィルターの例

4.1. クエリを格納する 2 つのファイルの共通部分を見つける

4.2. ハッシュカット


1.ビットマップ

1. ビットマップの概念

 いわゆるビットマップは、各ビットを使用して特定の状態を保存するもので、大量のデータがあり、データの重複がないシナリオに適しています。通常、あるデータが存在するかどうかを判断するために使用されます。

ビットマップの利点:

  • 高速
  • スペースを節約する

ビットマップの欠点:

  • 整数型のみをマッピングでき、浮動小数点数、文字列などの他の型はマッピングを保存できません。

2. ビットマップの実装

2.1、ビットマップ構造

ビットマップ クラスの構造は次のとおりです。

template<size_t N>
class bitset
{
public:
	bitset()
	{
		_bits.resize(N / 8 + 1, 0);
	}

    //将某个比特位置1
	void set(size_t x)
	{}
    
    //将某个比特位置0
	void reset(size_t x)
	{}

    //检查位图中某个比特位是否为1
    bool test(size_t x)
    {}

private:
	vector<char> _bits;
};

2.2、ビット位置 1

実装コード:

	void set(size_t x)
	{
		size_t i = x / 8;
		size_t j = x % 8;

		_bits[i] |= (1 << j);
	}

除算を使用して、配列のi 番目char型のxによってマップされるビット を計算します。係数を使用して、i charj番目のビットのxにマップされるビットを計算します。次に、ビットごとの OR演算を使用して、指定されたビット位置を 1 に設定します。

 ビット単位の OR 演算を実行する場合、1 は右ではなく j ビット左にシフトするために使用されることに注意してください。これは、人間の主観的な理解では、数字の配置が次のようになっているためです。

 しかし実際には、コンピュータの仮想層のストレージ ロジックでは、デジタル ストレージは次のようになります。

 左シフト、右シフトというのは、左右に移動するということではなく、高い位置、低い位置に移動するということです。したがって、ターゲット位置を見つけるには、右シフトではなく左シフトを使用する必要があります。

2.3、ビット位置 0

実装コード:

	void reset()
	{
		size_t i = x / 8;
		size_t j = x % 8;

		_bits[i] &= ~(1 << j);
	}

除算を使用して、配列のi 番目char型のxによってマップされるビット を計算します。係数を使用して、i char型のj番目のビットのxにマップされるビットを計算します。次に、ビットごとの NOT AND演算を使用して、指定されたビット位置を 1 に設定します。

2.4. ビットマップ内のビットの検出

実装コード:

	bool test(size_t x)
	{
		size_t i = x / 8;
		size_t j = x % 8;

		return _bits[i] & (1 << j);
	}

3. ビットマップの例

3.1. 1 回だけ出現する整数を検索する

設定状態: 0 回発生、状態は 00。1 回発生し、ステータスは 01 です。2 回以上発生すると、ステータスは 10 になります。

template<size_t N>
class twobitset
{
public:
	void set(size_t x)
	{
		// 00->01
		if (_bs1.test(x) == false
			&& _bs2.test(x) == false)
		{
			_bs2.set(x);
		}

		// 01->10
		else if (_bs1.test(x) == false
			&& _bs2.test(x) == true)
		{
			_bs1.set(x);
			_bs2.reset(x);
		}

		//10
	}

	void Print()
	{
		for (size_t i = 0; i < N; ++i)
		{
			if (_bs2.test(i))
			{
				cout << i << endl;
			}
		}
	}
public:
	bitset<N> _bs1;
	bitset<N> _bs2;
};

テスト結果は次のとおりです。 

3.2、2 つのファイルの共通部分を見つける

 方法 1 : いずれかのファイルの値をビットマップに読み込み、次に別のファイルを読み込み、上記のビットマップ内にあるかどうかを判断し、交差内にある場合は値を取り出し、対応するビットマップを 0 に設定します。

 方法 2 : 2 つのビットマップを作成し、ファイル 1 から読み取ったデータをビットマップ 1 にマッピングし、ファイル 2 から読み取ったデータをビットマップ 2 にマッピングします。次に、ビットマップ 1 とビットマップ 2 がビットごとに AND 演算されます。最終結果は交差点です。

3.3. 2 回以内に出現するすべての整数を検索する

 設定状態: 0 回発生、状態は 00。1 回発生し、ステータスは 01 です。2回発生、ステータスは10。3 回以上発生するとステータスは 11 になります。

実装コードは 3.1 のものと似ています。

2. ブルームフィルター

1. ブルームフィルターの提案

 ニュース クライアントを使用してニュースを視聴すると、継続的に新しいコンテンツが推奨され、推奨されるたびにそれが繰り返され、すでに見たコンテンツは削除されます。ここで、ニュース クライアント レコメンデーション システムはプッシュ重複排除をどのように実現するのかという疑問が生じます。サーバーには、ユーザーが閲覧したすべての履歴記録が記録され、レコメンデーション システムがニュースを推奨する際、各ユーザーの履歴記録がフィルタリングされ、既存の記録が除外されます。素早く見つけるにはどうすればよいでしょうか?

  1.  ハッシュ テーブルを使用してユーザー レコードを保存しますが、欠点はスペースの無駄です。
  2.  ユーザー レコードを保存するにはビットマップを使用します。欠点: ビットマップは通常、整形のみを処理できます。コンテンツ番号が文字列の場合は処理できません。
  3.  ハッシュとビットマップの組み合わせ、つまりブルーム フィルター。

2. ブルームフィルターの概念

 ブルーム フィルターは、1970 年にバートン ハワード ブルームによって提案されたコンパクトで賢い確率的データ構造です。効率的な挿入とクエリが特徴で、「何かが存在してはいけない、または存在する可能性がある」ことを示すために使用でき、複数のハッシュ関数を使用します。データの一部をビットマップ構造にマッピングします。この方法では、クエリの効率が向上するだけでなく、メモリ領域も大幅に節約できます。

 ブルーム フィルターを使用すると、衝突の可能性を減らすことができます。値を 1 か所にマッピングすると誤判定が発生しやすくなりますが、複数の位置にマッピングすることで誤判定率を軽減できます。

ブルームフィルターの利点:

  • 要素の追加とクエリの時間計算量は O(K) (K はハッシュ関数の数で、通常は比較的小さい) であり、データのサイズとは関係ありません。
  • ハッシュ関数は互いに無関係であるため、ハードウェアの並列処理に便利です。
  • ブルーム フィルターは要素自体を保存する必要がないため、厳密な機密性が必要な場合に大きな利点があります。
  • ある程度の誤った判断に耐えることができる場合、ブルーム フィルターは他のデータ構造に比べてスペース面で大きな利点があります。
  • データ量が多い場合、ブルーム フィルターは完全なセットを表すことができますが、他のデータ構造ではそれができません。
  • 同じハッシュ関数のセットを使用するブルーム フィルターは、交差、和集合、および差分の演算を実行できます。

 ブルームフィルターの欠点:

  • 偽陽性率が存在します。つまり、偽陽性 (False Position) が存在します。つまり、要素がセット内にあるかどうかを正確に判断することができません (解決策: 誤って判断される可能性のあるデータを保存するホワイト リストを作成します) )。
  • 要素自体を取得できません。
  • 通常、ブルーム フィルターから要素を削除することはできません。
  • 削除にカウントを使用する場合、カウントの折り返しの問題が発生する可能性があります。

3. ブルームフィルターの実装

3.1、ブルームフィルターの挿入

 ブルームフィルターに挿入:「baidu」:

 ブルームフィルターに挿入:「tencent」: 

 

 実装コード:

struct BKDRHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash += ch;
			hash *= 31;
		}
		return hash;
	}
};

struct DJBHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};

struct APHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (long i = 0; i < s.size(); i++)
		{
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ s[i] ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ s[i] ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

template<size_t N, class K = string, class Hash1 = BKDRHash, class Hash2 = APHash, class Hash3 = DJBHash>
class BloomFilter
{
public:
	void set(const K& key)
	{
		size_t len = N * _X;

		size_t hash1 = Hash1()(key) % len;
		_bs.set(hash1);

		size_t hash2 = Hash2()(key) % len;
		_bs.set(hash2);

		size_t hash3 = Hash3()(key) % len;
		_bs.set(hash3);
	}
private:
	static const size_t _X = 4; // 布隆过滤器的长度与数据数量的倍数关系
	bitset<N*_X> _bs;  //这样可以有效的减少不同数据间的冲突
};

3.2、ブルームフィルター検索

 ブルーム フィルターの考え方は、複数のハッシュ関数を使用して要素をビットマップにマッピングすることであるため、マッピングされた位置のビットは 1 でなければなりません。したがって、次の方法で検索できます: 各ハッシュ値に対応するビット位置が 0 として格納されているかどうかを計算します。1 つが 0 である限り、その要素はハッシュ テーブルに存在してはならないことを意味します。ハッシュ表。

実装コード:

bool test(const K& key)
{
	size_t len = N * _X;

	size_t hash1 = Hash1()(key) % len;
	if (!_bs.test(hash1))
		return false;

	size_t hash2 = Hash2()(key) % len;
	if (!_bs.test(hash2))
		return false;

	size_t hash3 = Hash3()(key) % len;
	if (!_bs.test(hash3))
		return false;

	return true;

	//依然存在误判,有可能把不在的判断成在
}

 なお、3つのハッシュ関数を用いて判定したとしても、誤判定の可能性はある。データが存在しないと判断された場合には、そのデータは存在しないはずである。データが存在すると判定された場合には、データが存在しない可能性もある。

 したがって、ブルーム フィルターは、ビデオ プッシュなどの誤った判断が許容されるシナリオにのみ適用できます。誤った判断が許容されない一部のシナリオでは、ブルーム フィルターにも対応するソリューションがあります。データが存在すると判断された場合は、2 回目の確認のためにデータベースに移動し、データがまだ存在する場合は、exists を返します。存在しない場合は、存在しないことを返します。

 ハッシュ関数の数は値に何ビットをマッピングするかを表し、ハッシュ関数の数が多いほど誤判定率は低くなりますが、ハッシュ関数の数が多いほど平均占有空間が大きくなります。

3.3、ブルームフィルターの削除

1 つの要素が削除されると、他の要素が影響を受ける可能性があるため、ブルーム フィルターは削除を直接サポートできません。

  削除をサポートする方法: ブルーム フィルターの各ビットを小さなカウンタに拡張し、要素を挿入するときに k 個のカウンタ (k 個のハッシュ関数によって計算されたハッシュ アドレス) に 1 を加算し、要素を削除するときに k 個のカウンタを 1 減算し、増加します。削除操作では数倍のストレージ容量が必要になります。

欠陥:

  1. 要素が実際にブルーム フィルター内にあるかどうかを確認する方法はありません。
  2. カウントラップアラウンドがあります。

4. ブルームフィルターの例

4.1. クエリを格納する 2 つのファイルの共通部分を見つける

ハッシュ セグメンテーションを使用して、大きなファイルを複数の小さなファイルに分割し、小さなファイルを交差させます。

 この方法を使用すると、均等に分割されていないため競合が多くなり、特定の Ai と Bi の小さなファイルが大きすぎるという問題が発生します。この問題が発生する状況は 2 つだけです。

  1. 1 つのファイル内に、多数のクエリが繰り返されます。
  2. 1 つのファイル内に、多数の異なるクエリが存在します。

unowned_set/set を直接使用し、ファイル クエリを順番に読み取り、セットに挿入できます。

  1. 小さなファイル全体のクエリが読み取られた場合、セットは正常に挿入できます。これは、最初のケースであることを意味します。
  2. 小さなファイル全体のクエリが読み取られ、挿入プロセス中に例外がスローされた場合、それは 2 番目のケースを意味します。他のハッシュ関数に変更し、再度分割して、交差部分を見つけます。

説明: Set はキーを挿入します (キーが既に存在する場合) は false を返します。メモリが使い果たされると、 bad_alloc 例外がスローされ、残りは成功します。

4.2. ハッシュカット

 ログ ファイルのサイズが 100G を超える場合、IP アドレスがログに保存され、最も多く出現する IP アドレスを見つけるアルゴリズムが設計されています。

引き続きハッシュ カット方法を使用します。

 小さなファイルを順番に処理し、unowned_map または map を使用して ip の出現数をカウントします。

  1.  統計プロセス中に例外がスローされない場合、統計は正常になります。小さなファイルを数えた後、最も多くのレコードが含まれるファイルです。メモリをクリアし、次の小さなファイルをカウントします。
  2.  統計プロセス中に例外が発生した場合は、1 つのファイルが大きすぎて競合が多すぎることを意味します。他のハッシュ関数に変更して、再度分割します。

 k データの小さなヒープを作成し、カウントされるたびにその小さなヒープを挿入して、最終的に解くことができる topK 問題に変換します。

おすすめ

転載: blog.csdn.net/weixin_74078718/article/details/131026316
おすすめ