[Estructura de datos] Aplicación hash

Tabla de contenido

1. Mapa de bits

1. Concepto de mapa de bits

2. Implementación de mapa de bits

2.1, estructura de mapa de bits

2.2, posición de bit 1

2.3, posición de bit 0

2.4 Detección de bits en el mapa de bits

3. Ejemplo de mapa de bits

3.1 Encuentra números enteros que aparecen solo una vez

3.2 Encuentra la intersección de dos archivos

3.3 Encuentra todos los números enteros que no aparecen más de 2 veces

2. Filtro de floración

1. Filtro Bloom propuesto

2. Concepto de filtro Bloom

3. Implementación del filtro Bloom

3.1, Inserción del filtro Bloom

3.2, búsqueda de filtro Bloom

3.3, Eliminación del filtro Bloom

4. Ejemplo de filtro Bloom

4.1 Encuentra la intersección de dos archivos que almacenan la consulta

4.2 Corte de hachís


1. Mapa de bits

1. Concepto de mapa de bits

 El llamado mapa de bits consiste en utilizar cada bit para almacenar un determinado estado, lo que es adecuado para escenarios con datos masivos y sin duplicación de datos. Por lo general, se utiliza para juzgar si un determinado dato existe o no.

Ventajas de los mapas de bits:

  • alta velocidad
  • ahorra espacio

Desventajas de los mapas de bits:

  • Solo se pueden asignar tipos enteros y otros tipos, como números de coma flotante, cadenas, etc., no pueden almacenar asignaciones.

2. Implementación de mapa de bits

2.1, estructura de mapa de bits

La estructura de la clase de mapa de bits es la siguiente:

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, posición de bit 1

Código de implementación:

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

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

 Calcule el bit mapeado por x en el i-ésimo tipo de carácter de la matriz con división. Utilice el módulo para calcular el bit asignado a x en el j-ésimo bit del tipo de carácter i . Luego use la operación OR bit a bit para establecer la posición de bit especificada en 1.

 Cabe señalar que cuando se realiza una operación OR bit a bit, se utiliza 1 para desplazar j bits a la izquierda, no a la derecha. Esto se debe a que, en nuestra comprensión subjetiva humana, la disposición de los dígitos es la siguiente:

 Pero de hecho, en la lógica de almacenamiento de la capa virtual de la computadora, el almacenamiento digital es así:

 Lo que queremos decir con desplazamiento a la izquierda y desplazamiento a la derecha no es moverse a la izquierda oa la derecha, sino moverse a una posición más alta oa una posición más baja. Entonces, para encontrar la posición de destino, debe usar el desplazamiento a la izquierda, no el desplazamiento a la derecha.

2.3, posición de bit 0

Código de implementación:

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

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

 Calcule el bit mapeado por x en el i-ésimo tipo de carácter de la matriz con división. Utilice el módulo para calcular el bit asignado a x en el j-ésimo bit del tipo de carácter i . Luego use la operación NOT AND bit a bit para establecer la posición de bit especificada en 1.

2.4 Detección de bits en el mapa de bits

Código de implementación:

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

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

3. Ejemplo de mapa de bits

3.1 Encuentra números enteros que aparecen solo una vez

Estado de configuración: Ocurre 0 veces, el estado es 00. Ocurre 1 vez, el estado es 01. 2 o más ocurrencias, el estado es 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;
};

Los resultados de la prueba son los siguientes: 

3.2 Encuentra la intersección de dos archivos

 Método 1 : lea el valor de uno de los archivos en el mapa de bits y luego lea otro archivo, juzgue si está en el mapa de bits anterior, si está en la intersección, saque el valor y establezca el mapa de bits correspondiente en 0.

 Método 2 : cree dos mapas de bits, asigne los datos leídos del archivo 1 al mapa de bits 1 y asigne los datos leídos del archivo 2 al mapa de bits 2. Luego, el mapa de bits 1 y el mapa de bits 2 son AND bit a bit. El resultado final es una intersección.

3.3 Encuentra todos los números enteros que no aparecen más de 2 veces

 Estado de configuración: Ocurre 0 veces, el estado es 00. Ocurre 1 vez, el estado es 01. Ocurre 2 veces, el estado es 10. 3 o más ocurrencias, el estado es 11.

El código de implementación es similar al de 3.1.

2. Filtro de floración

1. Filtro Bloom propuesto

 Cuando usamos el cliente de noticias para ver las noticias, continuamente nos recomendará contenido nuevo, y lo repetirá cada vez que lo recomiende, y eliminará el contenido que ya se ha visto. Aquí surge la pregunta: ¿cómo realiza el sistema de recomendación de clientes de noticias la deduplicación de inserción? El servidor registra todos los registros históricos que el usuario ha visto, cuando el sistema de recomendación recomienda noticias, filtrará los registros históricos de cada usuario y filtrará aquellos registros que ya existen. ¿Cómo encontrarlo rápidamente?

  1.  Use una tabla hash para almacenar registros de usuarios, desventaja: desperdicio de espacio.
  2.  Use un mapa de bits para almacenar registros de usuario. Desventajas: los mapas de bits generalmente solo pueden manejar la configuración. Si el número de contenido es una cadena, no se puede procesar.
  3.  Combinando un hash con un mapa de bits, es decir, un filtro de floración.

2. Concepto de filtro Bloom

 El filtro Bloom es una estructura de datos probabilística compacta e inteligente propuesta por Burton Howard Bloom en 1970. Se caracteriza por una inserción y consulta eficientes, y se puede usar para decirle "algo no debe existir o puede existir", utiliza múltiples funciones hash para mapear una pieza de datos en una estructura de mapa de bits. Este método no solo puede mejorar la eficiencia de las consultas, sino también ahorrar mucho espacio en la memoria.

 Los filtros Bloom pueden reducir la probabilidad de colisiones. Es fácil juzgar mal si un valor se asigna a una ubicación, y la tasa de juicio erróneo se puede reducir asignando varias ubicaciones.

Ventajas de los filtros Bloom:

  • La complejidad temporal de agregar y consultar elementos es: O(K), (K es el número de funciones hash, generalmente relativamente pequeñas), y no tiene nada que ver con el tamaño de los datos.
  • Las funciones hash no tienen nada que ver entre sí, lo cual es conveniente para el funcionamiento paralelo del hardware.
  • El filtro Bloom no necesita almacenar el elemento en sí, lo que tiene grandes ventajas en algunas ocasiones que requieren estricta confidencialidad.
  • Cuando es capaz de soportar una cierta cantidad de errores de juicio, el filtro Bloom tiene una gran ventaja de espacio sobre otras estructuras de datos.
  • Cuando la cantidad de datos es grande, el filtro Bloom puede representar el conjunto completo, pero otras estructuras de datos no pueden.
  • Los filtros Bloom que utilizan el mismo conjunto de funciones hash pueden realizar operaciones de intersección, unión y diferencia.

 Desventajas de los filtros Bloom:

  • Hay una tasa de falsos positivos, es decir, hay un falso positivo (False Position), es decir, es imposible determinar con precisión si un elemento está en el conjunto (remedio: cree una lista blanca para almacenar datos que pueden ser mal juzgados ).
  • No se puede obtener el elemento en sí.
  • Los elementos no se pueden eliminar de los filtros de floración en general.
  • Si se usa el conteo para eliminar, puede haber un problema de conteo de ajuste.

3. Implementación del filtro Bloom

3.1, Inserción del filtro Bloom

 Insertar en el filtro Bloom: "baidu":

 Insertar en el filtro Bloom: "tencent": 

 

 Código de implementación:

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, búsqueda de filtro Bloom

 La idea del filtro Bloom es mapear un elemento en un mapa de bits con múltiples funciones hash, por lo que el bit de la posición mapeada debe ser 1. Entonces puede buscar de la siguiente manera: Calcule si la posición del bit correspondiente a cada valor hash se almacena como cero. Siempre que uno sea cero, significa que el elemento no debe estar en la tabla hash, de lo contrario puede estar en el tabla de picadillo.

Código de implementación:

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;

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

 Cabe señalar que incluso si se utilizan tres funciones hash para el juicio, todavía existe la posibilidad de un error de juicio. Si se juzga que los datos no existen, los datos no deben existir. Si se juzga que los datos existen, existe cierta posibilidad de que los datos no existan.

 Por lo tanto, el filtro Bloom solo se puede aplicar a escenarios que pueden tolerar errores de juicio, como video push, etc. Para algunos escenarios en los que no se toleran los errores de juicio, el filtro Bloom también tiene una solución correspondiente: si se juzga que los datos existen, irá a la base de datos para una segunda confirmación, si aún existen, volverá a existir, si no existe, devolverá no existe.

 El número de funciones hash representa cuántos bits se asignan a un valor. Cuantas más funciones hash, menor es la tasa de error de juicio, pero cuantas más funciones hash, mayor es el espacio medio ocupado.

3.3, Eliminación del filtro Bloom

Los filtros Bloom no pueden admitir directamente la eliminación, porque cuando se elimina un elemento, otros elementos pueden verse afectados.

  Un método que admita la eliminación: expanda cada bit en el filtro Bloom en un pequeño contador, agregue uno a k contadores (direcciones hash calculadas por k funciones hash) al insertar elementos y elimine elementos Cuando, disminuya los k contadores en uno y aumente la operación de eliminación ocupando varias veces más espacio de almacenamiento.

defecto:

  1. No hay forma de confirmar si el elemento está realmente en el filtro de floración.
  2. Hay conteo envolvente.

4. Ejemplo de filtro Bloom

4.1 Encuentra la intersección de dos archivos que almacenan la consulta

Use la segmentación hash para dividir un archivo grande en varios archivos pequeños y luego deje que los archivos pequeños se crucen:

 Al usar este método, debido a que no está dividido uniformemente, puede haber muchos conflictos y el problema de que un determinado archivo pequeño Ai y Bi es demasiado grande. Solo hay dos situaciones en las que se produce este problema:

  1. En un solo archivo hay una gran cantidad de consultas repetidas.
  2. En un solo archivo, hay una gran cantidad de consultas diferentes.

Puede usar directamente un set/set unordered_set, leer la consulta del archivo a su vez e insertarlo en el conjunto:

  1. Si se lee la consulta de todo el archivo pequeño, el conjunto se puede insertar con éxito, lo que significa que es el primer caso.
  2. Si se lee la consulta de todo el archivo pequeño y se lanza una excepción durante el proceso de inserción, significa el segundo caso. Cambie a otras funciones hash, divida nuevamente y luego encuentre la intersección.

Descripción: Set inserta la clave, si ya existe, devuelve falso. Si se agota la memoria, se lanzará una excepción bad_alloc y el resto se realizará correctamente.

4.2 Corte de hachís

 Dado un archivo de registro con un tamaño de más de 100 G, las direcciones IP se almacenan en el registro y se diseña un algoritmo para encontrar la dirección IP con la mayor cantidad de ocurrencias.

Todavía usando el método de corte de hash:

 Procese cada archivo pequeño por turno y use unordered_map o map para contar el número de ocurrencias de ip.

  1.  Si no se lanza ninguna excepción durante el proceso de estadísticas, las estadísticas serán normales. Después de contar un archivo pequeño, el que tenga más registros. Borre la memoria y cuente el siguiente archivo pequeño.
  2.  Si ocurre una excepción durante el proceso de estadísticas, significa que un solo archivo es demasiado grande y hay demasiados conflictos. Cambie a otras funciones hash y vuelva a dividir.

 Cree un pequeño montón de datos k, e inserte el pequeño montón cada vez que se cuente, y conviértalo en un problema topK, que finalmente se puede resolver.

Supongo que te gusta

Origin blog.csdn.net/weixin_74078718/article/details/131026316
Recomendado
Clasificación