ハッシュテーブルと文字列ハッシュの予備学習

1つ、ハッシュテーブル

ハッシュテーブルh []のストレージ構造は、O(1)の複雑さを持つ数を見つけることです。

マッピングはハッシュ関数f(x)によって完了し、ハッシュテーブルの添え字idxはキーから取得されます。idx= f(key);

次に、次のように格納します。h[idx] = key;

  1. ハッシュ関数を作成する:剰余法を除算して残す

    f(x)= x mod pは、xを0からp-1までの数値にマップします。

    その中で、pは素数と見なされるべきであり、2の累乗から可能な限り離れている必要があります。

  2. 競合の問題を解決します:f(key1)== f(key2)の場合

    1. ジッパー方式、1つのh [i]が単一リンクリストに接続されている
    2. オープンアドレス法では、競合が後方にシフトされるため、hの長さは通常、指定された範囲の2〜3倍として定義されます。

タイトル説明

コレクションを維持するために、次の操作がサポートされています。

「Ix」、数値xを挿入、
「Q x」、数値xがセットに含まれているかどうかを確認します。
ここで、N個の操作が実行され、対応する結果がクエリ操作ごとに出力されます。

入力形式
最初の行には、演算の数を表す整数Nが含まれています。

次のN行、各行には操作命令が含まれ、操作命令は「Ix」と「Qx」のいずれかです。

出力形式
クエリコマンド「Qx」ごとにクエリ結果を出力します。セットにxが含まれている場合は「はい」、それ以外の場合は「いいえ」を出力します。

各結果は1行を占めます。

データ範囲
1≤N≤10 5
-10 9 ≤x≤10 9

入力サンプル:

5
I 1
I 2
I 3
Q 2
Q 5

サンプル出力:

はい
いいえ

1.チェーンアドレス方式

オープンアドレス法と比較すると、ハッシュテーブルの長さは実際に必要な長さNと同じです。ただし、各位置は単一リンクリスト、合計N個の単一リンクリストにつながる必要があり、同じアドレスのすべてのf(key)は同じ単一リンクリストにマップされます。

したがって、ハッシュテーブルhの各場所は、キー値ではなく、ヘッドノードを含まない単一リンクリストである単一リンクリストを指すヘッドノードとして機能します。

N個の単一リンクリストの全長もNです。理想的には、各単一リンクリストにはノードが1つだけあります(競合はありません)。単一リンクリストは、私が以前に学習した配列シミュレーションを使用し、配列を使用して複数のリンクリストをシミュレートします(複数のヘッドノードがそれを指しているため)。

#include <iostream>
#include <cstring>

using namespace std;

const int N=1e5+3; "取大于1e5的第一个质数,要模质数"
int h[N]; //哈希表,作为head结点指向存储数值的e链表 ,冲突的存在同一位置
int e[N],ne[N],idx;
//这里可以从0开始存储,每个头结点都是单独拉出的head指向的
//ne[i]=-1作为链表结束的标志,表示后面没有链表了,为了避免单独考虑初始化的问题,h数组初始化为-1,初始表示空,指向结尾
//memset数组适合初始化0(00000000) -1(11111111)  0x3f3f3f3f3f  是按字节初始化
void insert(int x)
{
    
    
    "x属于-10^9到 10^9 ,模N后属于-(N-1)到N-1,要将其映射到正数的区间(0,N-1)"
    int k=(x%N+N)%N; "-(N-i)和i模N同余"
    e[idx]=x;
    ne[idx]=h[k];  //每次采用头插法
    h[k]=idx++;
}

bool find(int x)
{
    
    
    int k=(x%N+N)%N;
    for (int i = h[k]; i != -1; i = ne[i]) {
    
    
        if (e[i]==x)  return true;
    }
    return false;
}

int main()
{
    
    
    int n,x;
    char op[2];
    cin>>n;
    memset(h, -1, sizeof(h));  //头文件 cstring
    while (n--){
    
    
        cin>>op>>x;
        if (op[0]=='I'){
    
    
            insert(x);
        } else {
    
    
            if (find(x)) puts("Yes");
            else printf("No\n");
        }
    }
    return 0;
}

2.オープンアドレス法

値xを挿入するには、f(x)を使用して最初に挿入する位置を見つけます。

位置がnullの場合、その番号が配列に存在しないことを意味し、この添え字が挿入される位置です。

空ではなく、値がxでない場合は、線形検出シーケンス、 順番に1つ後ろに移動します、判断を続けます。

ループの終了には2つの可能性があります。1。挿入される位置を表すnullが検出されました。2。空でない場合は、値がxであり、数値が見つかったことを意味します。

:このメソッドのハッシュテーブルで定義される長さは、実際に必要な長さの2〜3倍である必要があります。

#include <iostream>
#include <cstring>

using namespace std;

const int N=2e5+3,null=0x3f3f3f3f;
int h[N];
//这次h数组里不存指向的链表了,而是就是存真实的数值,
//由于原数组值是-10^9到10^9,所以要取一个不是这个范围的数初始化h数组

int find(int x)
{
    
    
    int k=(x%N+N)%N;
    while (h[k]!=null && h[k]!=x){
    
    
        k++;
        if (k==N) k=0; //循环
    }
    //退出循环时,要么为空,要么h[k]=x,因此返回的k要么是插入的位置,要么是匹配的位置
    return k;
}

int main()
{
    
    
    int n,x;
    char op[2];
    cin>>n;
    memset(h, 0x3f, sizeof(h));  //头文件 cstring
    while (n--){
    
    
        cin>>op>>x;
        int k=find(x);
        if (op[0]=='I'){
    
    
            h[k]=x;
        } else {
    
    
            if (h[k]!=null) puts("Yes");
            else printf("No\n");
        }
    }
    return 0;
}

2、文字列ハッシュ

詳細については、青い部分を参照してください

目的:文字列照合に使用されるKMPと同じです。

アイデア:親文字列(添え字1から格納)が与えられた場合、長さはNであり、文字列はPベース番号と見なされます。各文字の値はASCIIコードにすることも、自分でエンコードすることもできます(1である必要があります)。コーディングを開始します)、次に、接頭辞の合計のアイデアに従って、それをN個の部分文字列に変換し、これらのN個の部分文字列の値を順番に見つけます。Pベース番号の下の対応する値、次に、配列の添え字1からNに格納されているQを法とします。

親文字列でlからrまでの文字列の対応する値を見つけます:h [r] -h [l-1]× pr-l + 1

通常P 131または13331を取る; Q 2かかり64

#include <iostream>

using namespace std;

typedef unsigned long long ULL;
const int N=1e5+10,P=131;
char str[N];
int l1,r1,l2,r2;
ULL h[N],p[N];//p[N]={1} 也可,除了第一位是1,其它都是0
//h[k]存储字符串前k个字母的哈希值 mod 2^64;h[0]=0,从下标1开始,前缀和思想
// p[k]存储 P^i,方便求子串,任何数的次方都为1,因此p[0]=1

ULL get_sub(int l,int r)
{
    
    
    return h[r]-h[l-1]*p[r-l+1];
}

int main()
{
    
    
    int n,m;//长度为n的字符串,再给定m个询问
    cin>>n>>m>>str+1;
    p[0]=1;
    //前缀和思想求P进制数
    for (int i = 1; i <= n; ++i) {
    
    
        p[i]=p[i-1]*P;//顺便求P^i
        h[i]=h[i-1]*P+str[i];  //由于字符串里有大写 小写 数字,直接就按照ASCII码运算,不再规定A为1了,当全是大写字母时,可以str[i]-'A'+1,即A从1开始编码,而不用ASCII码 
    }      //前缀求哈希值,每次做左移一位
    while (m--){
    
    
        cin>>l1>>r1>>l2>>r2;
        if (get_sub(l1,r1)==get_sub(l2,r2)) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }

    return 0;
}

文字列ハッシュを使用すると、文字列照合を実現するためにO(n)の複雑さを実現できますが、KMPアルゴリズムではO(n 2)の複雑さが必要ですが、文字列ハッシュにはエラーの可能性がありますが、非常に小さいです。
KMPテンプレート質問転送

#include <iostream>

using namespace std;

typedef unsigned long long ULL;
const int N=1e5+10,M=1e6+10,R=131; 模板串p  模式串s R进制
char s[M],p[N];  p短 s长,p匹配s
ULL h[M],r[M]={
    
    1}; 即r[0]=1,其它值为0
ULL P; 大写p存模板串的哈希值

int main() {
    
    
    int n,m;
    cin>>n>>p+1>>m>>s+1;
  求模板串p的哈希值
    for (int i = 1; i <= n; ++i)  P=P*R+p[i];
  求模式串s的前缀和
    for (int i = 1; i <= m; ++i) {
    
    
        r[i]=r[i-1]*R;
        h[i]=h[i-1]*R+s[i];
    }
    for (int i = 1; i <= m-n+1; ++i) {
    
    
        if (h[i+n-1]-h[i-1]*r[n]==P) cout<<i-1<<" ";
    }
    return 0;
}

おすすめ

転載: blog.csdn.net/HangHug_L/article/details/113770583