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;
-
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.
-
Resuelva el problema del conflicto: cuando f (tecla1) == f (tecla2)
- Método de cremallera, una h [i] está conectada a una lista enlazada individualmente
- 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;
}