ハッシュ(ハッシュハッシュ)

序文

ハッシュは、一般的に使用されるアルゴリズムの1つです。
ハッシュを理解するために、最初に簡単な問題を見てみましょう。
トピック:

给出N个正整数,再给出M个正整数,问这M个数中的每个数分别是否在N个数中出现过,
其中N,M<=100000,且每个正整数均不超过100000。
例:
M=3  (1,2,4)   N=3  (1,2,7)
1,2出现过  7没有出现过

最も簡単なアイデアは、Mに数値を入力するときにNを1回トラバースすることです。このアルゴリズムの複雑さはO(MN)です
。MとNが大きい場合、アルゴリズムは明らかに遅すぎます。
では、どのように実行しますか?時間にスペースを使用
して、ブール配列hashTable [100010]設定しましょうここで、hashTable [x] = trueは、正の整数xがNに出現したことを意味し、
hashTable [x] = falseは、正の整数xはにありますそれはNに現れませんでした。
このようにして、最初にN個の正の整数が読み取られたとき、つまり読み取られた数値がxのとき、hashTable [x] = trueにします
(注:hashTable配列はfalseに初期化する必要があります。つまり、初期化された状態にあること番号は表示されていません)。
したがって、照会するM番号については、hashTable配列を介して、各番号が出現したかどうかを直接判断できます。
このアルゴリズムの複雑さはO(N + M)です。
コードは次のように表示されます。

#include<cstdio>
#include<algorithm>
using namespace std;
bool a[100010]={
    
    false};
int main(void)
{
    
    
  	int M,N;
  	int N_number,M_number;
  	int i,j;
  	scanf("%d%d",&N,&M);
  	for(i=0;i<N;i++)
  	{
    
    
  		scanf("%d",&N_number);
  		a[N_number]=true;
  	}
  	for(i=0;i<M;i++)
  	{
    
    
  		scanf("%d",&M_number);
  		if(a[M_number]==true)
  			printf("Yes\n");
  		else
  			printf("No\n");
  	}
    return 0;
}

質問がMの数を見つけることである場合、Nの数の出現数。
配列をint型として定義し、見つかったらhashTable [x] ++を定義するだけで済みます。
コードは次のとおりです。

#include<cstdio>
#include<algorithm>
using namespace std;
int a[100010]={
    
    0};
int main(void)
{
    
    
  	int M,N;
  	int N_number,M_number;
  	int i,j;
  	scanf("%d%d",&N,&M);
  	for(i=0;i<N;i++)
  	{
    
    
  		scanf("%d",&N_number);
  		a[N_number]++;
  	}
  	for(i=0;i<M;i++)
  	{
    
    	
  		scanf("%d",&M_number);
  		printf("%d\n",a[M_number]);
  	}
    return 0;
}

上記の2つの問題には機能があります。つまり、入力番号を配列の添え字として直接使用して、番号の性質をカウントします(このアプローチは非常に実用的です。覚えておいてください)。

ハッシュ紹介

上記の演習を行うと、入力番号が大きすぎる(10 9)か、入力が文字列であるため、配列の添え字として直接使用できないという問題があることがわかります
これらの厄介な要素を許容範囲内の整数に変換する方法があれば、それは素晴らしいことです。

それでハッシュが生まれました。一般的に言えば、ハッシュは、「関数を介して要素を整数に変換し、整数がこの要素を可能な限り一意に表すことができるようにする」という文に凝縮できます。その中で、この変換関数はハッシュ関数Hつまり、要素が変換前のキーである場合、変換後は整数H(キー)になります。
では、キーが整数の場合、一般的に使用されるハッシュ関数は何ですか?
一般的に言えば、一般的に使用される方法は、直接アドレス指定方法、
真ん中の方法をとる正方形、剰余を分割して残す方法などです。これらの中で、直接アドレス指定方法は恒等変換を参照します(つまり、H(key)= key、問題このセクションの冒頭では、キーを直接配置します。配列の添え字として、これは最も一般的で実用的なハッシュアプ​​リケーションです)または線形変換(つまり、H(key)= a * key + b);および二乗法キーの正方形の中央がハッシュ値として使用されることを意味します(めったに使用されません)。
剰余を除算して残す方法の方が実用的です。
Chenliuの剰余法とは、鍵を数値modで除算して、剰余をハッシュ値として取得する方法、つまりH(key)= key%modを指します。
このハッシュ関数を使用すると、大きな数値をに変換できます。 modを超えない整数。実行可能な配列インデックスとして使用できるようにします(注:テーブルの長さTSizeはmodより小さくすることはできません。小さくしないと、範囲外になります)。もちろん、modが素数の場合、H(key)は[0、mod)の範囲のすべての数を可能な限りカバーできるため、便宜上、TSizeは以下の素数であり、modは直接取得されます。 TSizeが等しいため。

実際、問題が発生します。余りを除算して残す方法では、key1とkey2の2つの異なる数値が存在する可能性があります。それらのハッシュ値H(key1)とH(key2)は同じであるため、key1がすでにテーブル内の位置を次のように設定しています。H(key1)の単位が占有されると、key2はこの位置を使用できなくなります。この状況を「競合」と呼びます

紛争は避けられないので、紛争を解決する方法を見つけなければなりません。例として、競合を解決する3つの方法を示します。最初の方法と2番目の方法はどちらも、オープンアドレス法とも呼ばれる新しいハッシュ値を計算します。

  1. 線形プロービング:
    キーのハッシュ値H(key)が取得されたが、インデックスがH(key)であるテーブル内の位置が他の要素によって使用されている場合、次の位置H(key)+1が占有されている場合は、この位置を使用します。そうでない場合は、次の位置のチェックを続行します(つまり、ハッシュ値を1ずつ増やし続けます)。チェックプロセス中にテーブルの長さを超えた場合は、最初の位置に戻ります。使用できる場所が見つかるまで、またはテーブル内のすべての場所が使用されていることがわかるまで、テーブルのループを続行します。明らかに、このアプローチは簡単にバンチングにつながりますつまり、テーブル内のいくつかの連続した位置が使用され、効率がある程度低下します。

  2. 二乗プローブ法(二次プロービング)二乗プローブ法は、混雑現象を極力回避するために、位置H(キー)のテーブルインデックスが占有されている場合、次の順序でチェックします。テーブル:H(キー)+ 1 2、H(キー)-1 2、H(キー)+2 2、H(キー)-2 2、H(キー)+ 3 2、... if H(キー) + k 2がテーブルの長さTSizeを取得した場合、H(key)+ k 2はテーブルの長さTSizeを法とします。検査プロセス中にH(key)-k 2 <0が発生した場合(テーブルの最初のビットを想定)が0)の場合、結果として(H(key)-k 2%TSize + TSize)%TSize(最初の非負の数が表示されるまでH(key)-k 2をTSizeに追加するのと同じ)。負の数の問題を回避したい場合は、正の二乗検出しか実行できません。kが[0、TSize)の範囲内の位置を見つけることができない場合、k≥TSizeの場合、位置を見つけてはならないことが証明できます。
  3. チェーンアドレス方式(ジッパー方式)
    は、上記の2つの方式とは異なり、新しいハッシュ値を計算するのではなく、同じH(key)を持つすべてのキーを単一リンクリストに接続します。このようにして、配列Linkを設定できます。範囲は、Link [0] 〜Link [mod-1]です。ここで、Link [h]は、H(key)= hの単一リンクリストを格納するため、ハッシュ値が複数のキーワードのすべてがhの場合、これらの競合するキーを単一リンクリストに直接接続できます。このとき、単一リンクリストをトラバースして、H(key)= hのすべてのキーを見つけることができます。
    もちろん、一般に、標準ライブラリテンプレートライブラリのマップを使用してハッシュ関数を直接使用できます(unordered_mapはC ++ 11の後で使用できます。これは高速です)。したがって、これらのメソッドをシミュレートする必要がない限り、またはアルゴリズムが必要です比較的高いです。通常、上記の競合解決方法を自分で実装する必要はありません。

初期文字列ハッシュ

キーが整数でない場合、ハッシュ関数はどのように設計する必要がありますか?

例として、2次元の整数点Pの座標を整数にマッピングして、整数点Pを整数で一意に表すことができるようにする方法があります。整数点Pの座標が(x、y)であり、0≤x、y≤Rangeであるとすると、ハッシュ関数はH§= X * Range + yに設定できるため、任意の2つの整数点PとP2 、H(P1)はH(P2)と等しくなりません。H(P)を使用して点P全体を一意に表し、整数ハッシュ法を使用してさらに狭い範囲にマッピングできます。

文字列ハッシュとは、文字列Sを整数にマッピングして、整数が文字列Sを可能な限り一意に表すことができるようにすることです。
議論を容易にするために、最初に文字列が大文字のAZで構成されていると仮定します。これに基づいて、A〜Zを0〜25と見なし、26の大文字が2つの16進システムに対応するようにします。次に、2つの16進法を10進法に変換するという考えによれば、システム変換の結論は、システム変換の過程で、取得された10進法が一意である必要があることを示しています。これにより、文字列をにマッピングできます。整数。要件(注:変換される最大整数は26 len -1です。ここで、lenは文字列の長さです)。
コードは次のように表示されます。

int hushFunc(char s[],int len)//将字符串转换成整数
{
    
    
	int id=0;
	for(int i;i<len;i++)
	{
    
    
		id=id*26+s[i]-'A';//将26进制转换为10进制
	}
	return id;
}

もちろん、文字列Sの長さが長いと、変換される整数も大きくなるため、長すぎないlenの使用に注意する必要があります。文字列に小文字が含まれている場合は 、A〜Zを0〜25、azを26〜51として使用できるため、基数52を基数10に変換する問題になり、方法は同じです。

int hushFunc(char s[]),int len)
{
    
    
	int id=0;
	for(int i;i<len;i++)
	{
    
    
		if(s[i]>='a'&&s[i]<='z')
		id=id*52+s[i]-'A'+26;
		else if(s[i]>='A'&&s[i]<='Z')
		id=id*52+s[i]-'A';
	}
	return id;
}

数字がある場合、それに対処する一般的に2つの方法があります。

  1. 小文字の扱いに合わせて、16進数を62に増やしてください。
  2. 文字列の末尾に一定の桁数があることが保証されている場合は、上記の考え方に従って、前の英字の部分を整数に変換し、最後の桁を直接接続することができます。 up。たとえば、3文字と1桁で構成される文字列「BCD4」の場合、最初に前の「BCD」を整数(1 26 26 + 2 * 26 + 3)731に変換してから、最後に直接接続できます。ビット4は7314になります

次のコードはこの例を反映しています。

int hushFunc(char s[]),int len)
{
    
    
	int id=0;
	for(int i;i<len-1;i++)//末位为数字,所以排除末位
	{
    
    
		if(s[i]>='a'&&s[i]<='z')
		id=id*52+s[i]-'A'+26;
		else if(s[i]>='A'&&s[i]<='Z')
		id=id*52+s[i]-'A';
	}
	id=id*10+s[len-1]-'0';
	return id;
}

練習問題

N個の文字列(正確に3つの大文字で構成される)が与えられると、M個のクエリ文字列が与えられ、各クエリ文字列がN個の文字列に表示される回数。

#include<cstdio>
int hushF(char a[],int len)
{
    
    
	int id=0;
	for(int i=0;i<len;i++)
	{
    
    
		id=id*26+a[i]-'A';
	}
	return id;
}
int hush[26*26*26+10];
char s[100][5],temp[5];
int main(void)
{
    
    
	int M,N;
	int i;
	scanf("%d%d",&N,&M);
	for(i=0;i<N;i++)
	{
    
    
		scanf("%s",s[i]);
		hush[hushF(s[i],3)]++;
	}
	for(i=0;i<M;i++)
	{
    
    
		scanf("%s",temp);
		printf("%d\n",hush[hushF(temp,3)]);
	}
}

おすすめ

転載: blog.csdn.net/qq_46527915/article/details/114670948