Aprendizaje preliminar de la tabla hash y la cadena hash

Uno, tabla hash

La estructura de almacenamiento de la tabla hash h [] es encontrar un número con complejidad O (1).

El mapeo se completa con la función hash f (x), y el subíndice idx en la tabla hash se obtiene de la clave: idx = f (clave);

Luego almacene: h [idx] = clave;

  1. Construya una función hash: Divida y deje el método restante

    f (x) = x mod p asigna x a un número entre 0 y p-1.

    Entre ellos: p debe tomarse como un número primo , y debe estar lo más lejos posible de la potencia 2.

  2. Resuelva el problema del conflicto: cuando f (tecla1) == f (tecla2)

    1. Método de cremallera, una h [i] está conectada a una lista enlazada individualmente
    2. Método de direccionamiento abierto, los conflictos se desplazan hacia atrás, por lo que la longitud de h generalmente se define como 2 a 3 veces el rango especificado

Descripción del Título

Para mantener una colección, se admiten las siguientes operaciones:

"I x", inserte un número x;
"Q x", pregunte si el número x ha aparecido en el conjunto,
ahora se van a realizar N operaciones y se emite el resultado correspondiente para cada operación de consulta.

Formato de entrada La
primera línea contiene el entero N, que representa el número de operaciones.

Las siguientes N líneas, cada línea contiene una instrucción de operación, la instrucción de operación es una de "I x" y "Q x".

Formato de salida
Para cada comando de consulta "Q x", genera un resultado de consulta. Si x ha aparecido en el conjunto, genera "Sí", de lo contrario genera "No".

Cada resultado ocupa una línea.

Rango de datos
1≤N≤10 5
−10 9 ≤x≤10 9

Muestra de entrada:

5
Yo 1
Yo 2
Yo 3
Q 2
Q 5

Salida de muestra:

Si
no

1. Método de dirección en cadena

En comparación con el método de direccionamiento abierto, la longitud de la tabla hash es la misma que la longitud real N requerida. Pero cada posición debe conducir a una lista enlazada individualmente, un total de N listas enlazadas individualmente, y todas las f (tecla) para la misma dirección se mapean en la misma lista enlazada individualmente.

Por lo tanto, cada ubicación en la tabla hash h ya no es el valor clave, sino como un nodo principal que apunta a la lista enlazada individualmente, que es una lista enlazada individualmente que no contiene el nodo principal.

La longitud total de N listas enlazadas individualmente también es N. Idealmente, cada lista enlazada individualmente tiene un solo nodo (sin conflicto). La lista enlazada individual usa la simulación de matriz que he aprendido antes, y usa una matriz para simular varias listas enlazadas (porque hay varios nodos principales apuntando a ella).

#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. Método de direccionamiento abierto

Para insertar un valor x, encuentre la posición que se insertará por primera vez a través de f (x),

Si la posición es nula, significa que el número no existe en la matriz y este subíndice es la posición que se insertará;

Si no está vacío y el valor no es x, useSecuencia de detección lineal, En turnoRetroceder un lugar, Continuar juzgando.

Hay dos posibilidades para el final del ciclo : 1. Se encuentra un nulo, que representa la posición a insertar, 2. Si no está vacío, significa que el valor es x, y se ha encontrado el número.

Nota : La longitud definida por la tabla hash en este método debe ser de 2 a 3 veces la longitud real requerida.

#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;
}

Dos, hash de cadena

Consulte la parte azul para obtener más detalles .

Propósito : Lo mismo que KMP, utilizado para la coincidencia de cadenas.

Idea : Dada una cadena principal (almacenada desde el subíndice 1), la longitud es N y la cadena se considera un número base P. El valor de cada carácter puede ser su código ASCII, o puede codificarlo usted mismo (debe ser 1 Comience a codificar), y luego, de acuerdo con la idea de suma de prefijo, transfórmela en N subcadenas y encuentre los valores de estas N subcadenas a su vez.Valor correspondiente debajo del número base P, Luego módulo Q, almacenado en los subíndices 1 a N del arreglo.

Encuentre el valor correspondiente de la cadena de la r en la cadena principal: h [r] -h [l-1] × p r-l + 1

Por lo general, P toma 131 o 13331; Q toma 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;
}

El uso del hash de cadenas puede lograr una complejidad O (n) para lograr la coincidencia de cadenas, mientras que el algoritmo KMP requiere una complejidad O (n 2 ), pero el hash de cadenas tiene una probabilidad de error, aunque es extremadamente pequeña.
Transferencia de preguntas de plantilla de 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;
}

Supongo que te gusta

Origin blog.csdn.net/HangHug_L/article/details/113770583
Recomendado
Clasificación