哈希的应用--位图和布隆过滤器

news2024/11/15 14:01:47

在这里插入图片描述

哈希的应用--位图和布隆过滤器

  • 位图
    • 1. 位图概念
    • 2. 位图在实际中的应用
    • 3. 位图相似应用
      • 给定100亿个整数,如何找到只出现一次的整数?
      • 1个文件100亿int,1G内存,如何找到不超过2次的所有整数
  • 布隆过滤器
    • 1. 布隆过滤器的提出
    • 2. 布隆过滤器的插入
    • 3. 布隆过滤器的查找
    • 4. 布隆过滤器的删除
    • 5. 布隆过滤器的实现
    • 6. 布隆过滤器优点
    • 7. 布隆过滤器缺陷
    • 8. 布隆过滤器的应用

位图

1. 位图概念

位图(Bitset)是一种数据结构,用于表示一组布尔值,其中每个元素通常对应于一个位或一个二进制值,可以存储0或1。位图在计算机科学和计算机工程中经常用于各种应用,特别是在位级别的标志、掩码和快速查找中。以下是位图的一些关键特点:

  1. 二进制表示:位图中的每个元素都只能存储两个值,通常是0和1。这使得位图非常高效,因为每个位只需要一个二进制位来表示。
  2. 位操作:位图支持各种位操作,包括设置位、清除位、翻转位和查询特定位的操作。
  3. 空间效率:位图在表示大量布尔值时非常节省内存,因为每个位只需要一个二进制位。这使得位图在大规模数据处理中非常有用。
  4. 快速查找:位图用于快速查找元素的存在或不存在。通过检查位的值,可以快速确定元素是否在集合中。
  5. 位运算:位图支持位级别的位运算,如与、或、异或等,这些运算可用于合并和操作多个位图。

应用领域包括:

  • 位掩码:用于将一组标志或选项组合在一起,并进行快速检查和设置。
  • 布尔向量:用于高效存储和操作布尔值集合。
  • 集合操作:用于执行集合操作,如并集、交集和差集。
  • 压缩和编码:用于压缩数据,如运行长度编码(Run-Length Encoding)等。

2. 位图在实际中的应用

某大厂给过这样一道面试题,具体如下

给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中

你可能最开始想遍历?那肯定是不行的,因为40亿个无符号数占用将近15G内存。

那使用散列表呢?那这里使用的空间能在内存上吗?显然不可以,就算可以

  • 散列表需要大量内存,因为需要为40亿个整数维护哈希表。
  • 哈希冲突可能会导致性能下降,需要解决冲突。
  • 需要选择合适的哈希函数以避免碰撞。

又会遇到各种各样的问题

那么这里就可以用到我们上面提到的位图概念

位图(Bitset)方法

  • 创建位图:首先,创建一个足够大的位图,以能够表示您的整数范围。例如,如果整数范围在0到4,000,000,000之间,可以创建一个长度为4,000,000,001的位图。
  • 插入数据:将这40亿个无符号整数中的每个整数映射到位图的相应位置,并将对应的位设置为1。
  • 查询数据:当需要判断一个数是否在这40亿个数中时,只需查看位图中相应位置的位。如果该位为1,表示该整数在集合中;如果该位为0,表示不在集合中。

优点

  • 位图非常节省内存,因为每个整数只需要一个位。
  • 不需要哈希函数,也不需要解决哈希冲突。

这里我们只需要不到500M就很好的解决了这个问题,听起来挺理想的,那么应该如何来实现它呢?

代码如下

template<size_t N>
class bitset
{
public:
    bitset()
    {
        _bits.resize(N/8+1, 0);
    }

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

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

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

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

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


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

private:
    vector<char> _bits;
};

模板的作用就是用来传需要查询数的范围,比如我们这里有40亿怎么传值呢?

因为是无符号数,而无符号数范围是0-42亿左右,那我们不妨传入最大值,即-1

bitset<-1> bs1;

你可能会有疑问,多了两亿不会多很多内存吗?

答案是不会,占满其实也就512M,具体我们看下面的实现

我们先看成员变量的建立,由于在编程语言中,我们没有按位存储的存储类型(至少C/C++是没有的)

所以这里我们采用最小存储类型char,char占1个字节,可是我们需要的是1bit,那么我们该如何做呢?

接下来我们看位图的构造函数,我们用模板参数/8,初始化每个字节为全0,多+1是由于数组的索引从0开始,所以需要额外的一个元素来确保能够容纳最高索引 N-1 的位,这样我们的空间开辟完毕,也初始化完毕

接下来我们看set函数(此函数用于入位图)

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

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

用于将指定索引 x 处的位设置为1:

  • size_t i = x / 8;:这一行代码计算索引 x 对应的字节(byte)索引。因为 _bits 是一个 vector<char>,每个元素代表一个字节(8位),所以我们用索引 x 除以8来得到字节索引。
  • size_t j = x % 8;:这一行代码计算索引 x 对应的位在字节中的位置。它使用取模运算来获得余数,表示在字节中的位偏移。
  • _bits[i] |= (1 << j);:这一行代码使用位操作将位图中索引 x 处的位设置为1。具体来说:
    • (1 << j) 会创建一个只有第 j 位为1的整数。例如,如果 j 是3,那么 (1 << 3) 将得到二进制 00001000
    • _bits[i] |= (1 << j) 利用位或运算符将 _bits[i] 中对应的位和上面的整数进行“或”操作,将指定位设置为1。

这个函数的目的是在位图中设置指定索引 x 处的位为1,从而表示该位置存在某种标记或状态。

reset函数(此函数用于出位图)

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

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

用于将指定索引 x 处的位重置为0:

  • size_t i = x / 8;:这一行代码计算索引 x 对应的字节(byte)索引,以确定所在的字节。
  • size_t j = x % 8;:这一行代码计算索引 x 对应的位在字节中的位置,以确定位的偏移。
  • _bits[i] &= ~(1 << j);:这一行代码使用位操作将位图中索引 x 处的位重置为0。具体来说:
    • (1 << j) 会创建一个只有第 j 位为1的整数。例如,如果 j 是3,那么 (1 << 3) 将得到二进制 00001000
    • ~(1 << j) 会创建一个只有第 j 位为0、其余位为1的整数,以便将第 j 位重置为0。
    • _bits[i] &= ~(1 << j) 利用位与运算符将 _bits[i] 中对应的位和上面的整数进行“与”操作,将指定位设置为0。

这个函数的目的是在位图中重置指定索引 x 处的位为0,从而表示该位置不存在某种标记或状态。

test函数(此函数用于查询数是否在位图中)

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


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

用于检查指定索引 x 处的位是否为1:

  • size_t i = x / 8;:这一行代码计算索引 x 对应的字节(byte)索引,以确定所在的字节。
  • size_t j = x % 8;:这一行代码计算索引 x 对应的位在字节中的位置,以确定位的偏移。
  • _bits[i] & (1 << j):这一行代码使用位操作检查位图中索引 x 处的位是否为1。具体来说:
    • (1 << j) 会创建一个只有第 j 位为1的整数。例如,如果 j 是3,那么 (1 << 3) 将得到二进制 00001000
    • _bits[i] & (1 << j) 利用位与运算符将 _bits[i] 中对应的位和上面的整数进行“与”操作,以检查该位是否为1。如果结果为0,表示该位为0;如果结果为非0,表示该位为1。

这个函数的目的是检查位图中指定索引 x 处的位是否为1,以判断某种标记或状态是否存在。如果该位为1,函数返回true,表示存在;如果该位为0,函数返回false,表示不存在。

实际上在C++中是加入了位图这个容器的,名称和我这一样,这里我们模拟实现是为了更好的理解这个概念

在这里插入图片描述

库中的bitset功能更多,但主要函数也是这几个

在这里插入图片描述

3. 位图相似应用

给定100亿个整数,如何找到只出现一次的整数?

其实这种问题和我们上面的示例的题目类似,解决方式只需略作改动。

我们可以使用两个位图分别标记存在次数,也就类似key value型,这里我们只需要标记3种情况

在这里插入图片描述

代码如下

template<size_t N>
class twobitset
{
public:
    void set(size_t x)
    {
        bool inset1 = _bs1.test(x);
        bool inset2 = _bs2.test(x);

        // 00
        if (inset1 == false && inset2 == false)
        {
            // -> 01
            _bs2.set(x);
        }
        else if (inset1 == false && inset2 == true)
        {
            // ->10
            _bs1.set(x);
            _bs2.reset(x);
        }
        else if (inset1 == true && inset2 == false)
        {
            // ->11
            _bs1.set(x);
            _bs2.set(x);
        }
    }

    void print_once_num()
    {
        for (size_t i = 0; i < N; ++i)
        {
            if (_bs1.test(i) == false && _bs2.test(i) == true)
            {
                cout << i << endl;
            }
        }
    }

private:
    bitset<N> _bs1;
    bitset<N> _bs2;
};

这里我们需要实现一个双位图(Two-Bitset)数据结构。双位图是一种在每个索引上存储两个位的数据结构,通常用于表示每个索引的两种状态。代码解析如下:

  1. set(size_t x) 函数:
    • 首先,它检查 _bs1_bs2 位图中索引 x 处的状态。
    • 如果 _bs1 中索引 x 处的位为0,而 _bs2 中索引 x 处的位也为0(00状态),则将 _bs2 中索引 x 处的位设置为1,将其状态变为01。
    • 如果 _bs1 中索引 x 处的位为0,而 _bs2 中索引 x 处的位为1(01状态),则将 _bs1 中索引 x 处的位设置为1,将 _bs2 中索引 x 处的位重置为0,将其状态变为10。
    • 如果 _bs1 中索引 x 处的位为1,而 _bs2 中索引 x 处的位为0(10状态),则将 _bs1_bs2 中索引 x 处的位都设置为1,将其状态变为11。
  2. print_once_num() 函数:
    • 这个函数用于打印那些状态为10(01状态的相反)的索引。这表示只在 _bs2 中为1而在 _bs1 中为0的索引。这些索引被认为在双位图中只出现一次。

1个文件100亿int,1G内存,如何找到不超过2次的所有整数

两个题目其实相似,无非就是多一种状态,原理同上

在这里插入图片描述

布隆过滤器

在上面我们用位图很好的解决了多重整数高效查询的问题,那么我们在面对字符串时,该如何解决呢?

1. 布隆过滤器的提出

布隆过滤器(Bloom Filter)是由布隆在1970年提出的,它是一种空间效率高、查询速度快的数据结构,主要用于判断一个元素是否属于一个集合。布隆过滤器的提出解决了在大规模数据集中进行高效查找的问题,特别是当内存或存储有限的情况下。

以下是布隆过滤器的提出背景和主要原理

提出背景: 在计算机科学中,一些常见的问题包括查找元素是否在某个集合中,如单词拼写检查、垃圾邮件过滤、URL检测等。传统的数据结构如散列表或树结构可以解决这些问题,但它们在存储和查询效率上存在一些限制,特别是在面对大规模数据集时。因此,布隆过滤器的提出是为了解决这些限制。

原理: 布隆过滤器的核心思想是使用一个位数组(Bit Array)和多个哈希函数。其主要工作步骤如下:

  1. 初始化:创建一个位数组,初始所有位为0,以及选择多个不同的哈希函数。
  2. 插入元素:当需要插入一个元素时,将该元素经过多个哈希函数的映射,得到多个不同的位,然后将这些位设置为1。
  3. 查询元素:当需要查询一个元素是否在集合中时,将该元素经过多个哈希函数的映射,得到多个位的位置,然后检查这些位是否都为1。如果所有位都为1,则认为元素可能存在于集合中;如果任何一个位为0,则可以确定元素不存在于集合中。

在这里插入图片描述

特点

  • 布隆过滤器具有高效的查询速度,因为它不需要实际存储元素本身,而只需要检查位数组中的位。
  • 布隆过滤器可能会出现误判,即元素被判断为存在于集合中,但实际上并不在,但不会有漏判,即如果元素不在集合中,布隆过滤器不会将其判断为存在。
  • 布隆过滤器的空间效率很高,因为它可以存储大量元素而占用很少的内存。

2. 布隆过滤器的插入

布隆过滤器的插入过程是其核心操作之一,用于将元素添加到布隆过滤器中,以便后续查询该元素是否存在于集合中。下面是布隆过滤器的插入过程的详细解释:

  1. 初始化:首先,创建一个布隆过滤器,它包括一个位数组(Bit Array)和多个哈希函数。位数组中的每个位都初始化为0。
  2. 插入元素:要将一个元素插入布隆过滤器,执行以下步骤:
    • 哈希函数:使用预先选择的多个哈希函数,将要插入的元素映射为多个不同的位索引。每个哈希函数都会生成一个位索引,通常使用不同的种子值来确保生成不同的位索引。
    • 设置位:对于每个哈希函数生成的位索引,将相应的位数组中的位设置为1。这表示元素已经存在于布隆过滤器中。
  3. 完成插入:一旦对元素执行了上述步骤,插入过程就完成了。元素已被记录在位数组中,以后可以查询该元素是否存在于集合中。

注意事项

  • 布隆过滤器不存储实际元素本身,只存储元素的哈希值或映射后的位索引。因此,插入操作是基于哈希值的,而不是实际元素。
  • 哈希函数的数量和质量对布隆过滤器的性能至关重要。更多的哈希函数通常意味着更低的误判率,但也需要更多的计算资源。选择哈希函数时应考虑平衡性能和内存占用。
  • 插入操作通常是快速的,因为它涉及位数组的直接设置,而不需要大量内存操作。这是布隆过滤器高效的一个原因之一。

插入过程使布隆过滤器记录了元素的存在,使后续的查询操作成为可能。查询操作会利用相同的哈希函数,检查位数组中的位来确定元素是否在集合中。这使得布隆过滤器在查找元素是否存在时非常高效,尤其适用于大规模数据集和资源有限的环境。

还需要注意的是,哈希参数当然是越多误判率越低,但是面临的问题就是内存也会越来越大,所以我们需要找到最合适的哈希函数个数,关于这一点,我们可以参考知乎大佬的一篇文章

知乎大佬文章链接

具体总结为

在这里插入图片描述
在这里插入图片描述

3. 布隆过滤器的查找

布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中

注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判

比如:在布隆过滤器中查找"alibaba"时,假设3个哈希函数计算的哈希值为:1、3、7刚好和其他元素的比特位重叠,此时布隆过滤器告诉该元素存在,但实该元素是不存在的,存在可能存在误判,但是不存在不会存在误判

4. 布隆过滤器的删除

布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素
比如:删除其中一个元素,如果直接将该元素所对应的二进制比特位置0,另一元素也被删除了,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠

一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作

5. 布隆过滤器的实现

在这里我们先实现3个哈希函数

struct HashBKDR
{
	// BKDR
	size_t operator()(const string& key)
	{
		size_t val = 0;
		for (auto ch : key)
		{
			val *= 131;
			val += ch;
		}

		return val;
	}
};

struct HashAP
{
	// BKDR
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (size_t i = 0; i < key.size(); i++)
		{
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ key[i] ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ key[i] ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

struct HashDJB
{
	// BKDR
	size_t operator()(const string& key)
	{
		size_t hash = 5381;
		for (auto ch : key)
		{
			hash += (hash << 5) + ch;
		}

		return hash;
	}
};
  1. BKDR哈希函数 (Bentley and Sedgewick, 1997):
    • BKDR哈希函数使用一个常数因子131,以及遍历字符串中的字符。
    • 对于每个字符,它将当前哈希值乘以131,然后加上字符的ASCII码值。
    • 最终,它返回计算得到的哈希值。
  2. AP哈希函数 (Arjen Lenstra and Endre Szemerédi’s hash function):
    • AP哈希函数使用一系列位运算和异或操作来处理字符串中的字符。
    • 对于每个字符,它会根据字符的位置(奇数或偶数)应用不同的位运算操作。
    • 最终,它返回计算得到的哈希值。
  3. DJB哈希函数 (Daniel J. Bernstein, 1991):
    • DJB哈希函数使用一个初始哈希值5381,以及遍历字符串中的字符。
    • 对于每个字符,它将当前哈希值左移5位,然后加上字符的ASCII码值。
    • 最终,它返回计算得到的哈希值。

布隆过滤器代码

// N表示准备要映射N个值
template<size_t N, 
class K = string, class Hash1 = HashBKDR, class Hash2 = HashAP, class Hash3 = HashDJB>
class BloomFilter
{
public:
	void Set(const K& key)
	{
		size_t hash1 = Hash1()(key) % (_ratio*N);
		//cout << hash1 << endl;

		_bits->set(hash1);

		size_t hash2 = Hash2()(key) % (_ratio*N);
		//cout << hash2 << endl;

		_bits->set(hash2);

		size_t hash3 = Hash3()(key) % (_ratio*N);
		//cout << hash3 << endl;

		_bits->set(hash3);
	}

	bool Test(const K& key)
	{
		size_t hash1 = Hash1()(key) % (_ratio*N);
		//cout << hash1 << endl;
		if (!_bits->test(hash1))
			return false; // 准确的

		size_t hash2 = Hash2()(key) % (_ratio*N);
		//cout << hash2 << endl;

		if (!_bits->test(hash2))
			return false; // 准确的

		size_t hash3 = Hash3()(key) % (_ratio*N);
		//cout << hash3 << endl;

		if (!_bits->test(hash3))
			return false;  // 准确的

		return true; // 可能存在误判
	}


private:
	const static size_t _ratio = 5;
	std::bitset<_ratio*N>* _bits = new std::bitset<_ratio*N>;
};
  • Set(const K& key)
    • Set 方法用于将元素插入布隆过滤器中。
    • 它分别使用三个哈希函数将元素映射到位数组中的三个位置,然后将这三个位置的位设置为1。
    • 这样,插入操作将三次设置位操作,将元素标记为存在于布隆过滤器中。
  • Test(const K& key)
    • Test 方法用于测试元素是否存在于布隆过滤器中。
    • 与插入相似,它使用三个哈希函数将元素映射到三个位数组位置,并检查这三个位置的位是否都为1。
    • 如果有一个位置的位为0,说明元素不在布隆过滤器中,返回 false
    • 如果三个位置的位都为1,可能存在误判,返回 true

这个布隆过滤器可以用于在快速查找集合中的元素是否存在,但需要注意,它存在误判的可能性,即可能会将不在集合中的元素判断为存在。_ratio 在这个布隆过滤器实现中是一个倍数,它决定了位数组的大小。位数组的大小是 _ratio * N,其中 N 表示准备要映射的值的数量。具体来说,这个倍数 _ratio 用于控制位数组的大小以平衡误判率和内存使用。增加 _ratio 会增加位数组的大小,从而减小误判率,但也会增加内存占用。减小 _ratio 会减小位数组的大小,降低内存占用,但也会增加误判率。

6. 布隆过滤器优点

  1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
  2. 哈希函数相互之间没有关系,方便硬件并行运算
  3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
  4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
  5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
  6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算

7. 布隆过滤器缺陷

  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
  2. 不能获取元素本身
  3. 一般情况下不能从布隆过滤器中删除元素
  4. 如果采用计数方式删除,可能会存在计数回绕问题

8. 布隆过滤器的应用

给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?与上题条件相同,如何找到top K的IP?如何直接用Linux系统命令实现?

使用布隆过滤器和哈希切割

  1. 建立布隆过滤器:首先,创建一个合适大小的布隆过滤器。布隆过滤器的位数组大小需要足够大,以容纳所有可能的IP地址。
  2. 遍历日志文件:逐行遍历日志文件,提取每行中的IP地址。
  3. 哈希切割:对每个提取的IP地址应用一种哈希函数来将其映射到布隆过滤器的位数组上。这可以使用布隆过滤器的 Set 方法来实现。
  4. 统计次数:同时,维护一个哈希表,其中键是IP地址,值是出现的次数。在哈希切割过程中,检查IP是否已存在于表中,如果存在,则增加其出现次数。
  5. 查询次数最多的IP:在遍历完整个日志文件后,您可以遍历字典,找到出现次数最多的IP地址。
  6. 查询top K的IP:要找到top K的IP地址,可以对字典按照出现次数进行排序,然后选择前K个IP地址。

使用Linux系统命令

Linux系统提供了一些强大的命令行工具来处理文本文件,可以使用这些工具来解决问题:

  1. 使用awk命令或grep命令提取日志文件中的IP地址。
  2. 使用sort命令对提取的IP地址进行排序。
  3. 使用uniq -c命令统计IP地址的出现次数。
  4. 使用sort -nr命令按出现次数对IP地址进行逆序排序。
  5. 使用head命令来获取top K的IP地址。

示例命令行操作

cat log_file.log | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" | sort | uniq -c | sort -nr | head -n K

这将列出出现次数最多的top K个IP地址。

无论使用哪种方法,都需要根据实际需求和性能要求来选择。使用布隆过滤器和哈希切割的方法可能需要编写自定义代码,但可以处理非常大的数据集。使用Linux系统命令则更加简单,但可能受限于系统资源和性能。

给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法

在这种情况下,由于内存有限,需要使用外部排序(external sorting)技术来处理这两个大型文件并找到它们的交集。外部排序是一种适用于大规模数据集的算法,其中数据无法完全加载到内存中。

精确算法

  1. 分别对两个文件进行外部排序:首先,将两个文件分别划分为多个小文件块,每个小文件块可以适应内存的大小。然后,对每个小文件块进行内部排序,以确保每个块内的数据是有序的。
  2. 合并排序的块:对于每个文件,使用归并排序等合并算法,逐个合并排序后的小块,以创建一个完全有序的大文件。
  3. 查找交集:一旦两个文件都有序,可以使用合并算法来查找它们的交集。比较两个文件中的元素,将相同的元素输出到结果文件中。

这是一个精确的算法,它可以找到确切的交集,但需要大量的磁盘I/O和计算时间,因为数据需要多次读取和写入磁盘。

近似算法: 在内存有限的情况下,使用布隆过滤器可以实现近似的交集查找。以下是近似算法的步骤:

  1. 构建两个布隆过滤器:对于每个文件,构建一个布隆过滤器。这需要一小部分内存。在构建布隆过滤器时,需要选择合适的哈希函数和位数组大小,以平衡内存使用和误判率。
  2. 查询交集:对于第一个文件的每个查询,检查是否在第二个布隆过滤器中。如果布隆过滤器中存在该查询,将其添加到结果集中。
  3. 结果集:结果集中包含两个文件的近似交集。

近似算法的主要优点是节省了内存,但它会引入误判。如果某个查询在第一个布隆过滤器中存在,但实际上不存在于第二个文件中,它仍然会出现在结果集中。误判率取决于布隆过滤器的参数选择。

无论使用哪种算法,都需要在性能和准确性之间做权衡。精确算法提供准确的结果,但需要更多的时间和资源。近似算法可以在有限内存下快速处理,但可能会引入一定程度的误判。

如何扩展BloomFilter使得它支持删除元素的操作

Bloom过滤器是一种概率数据结构,通常不支持元素的删除操作。要扩展Bloom过滤器以支持元素的删除,可以考虑使用一些额外的技巧。以下是一种可能的方法:

使用计数型Bloom过滤器

一种支持删除操作的变体是计数型Bloom过滤器(Counting Bloom Filter)。与标准Bloom过滤器不同,计数型Bloom过滤器中的每个位不是简单的0或1,而是一个计数器。计数器可以表示一个元素被插入的次数。

以下是如何扩展Bloom过滤器以支持删除元素的步骤:

  1. 初始化计数型Bloom过滤器:创建一个计数型Bloom过滤器,其中每个位都初始化为0。计数型Bloom过滤器的大小通常与标准Bloom过滤器相同。
  2. 插入元素:当要插入元素时,不再将相应的位设置为1,而是增加相应的计数器。每次插入操作会增加计数器的值。
  3. 查询元素:在查询操作中,检查计数器是否大于零。如果计数器大于零,则表示元素存在。这是因为每次插入操作都会增加计数器的值。
  4. 删除元素:要删除元素,可以递减相应的计数器。如果计数器变为零,元素就被标记为不存在。

注意:在计数型Bloom过滤器中,可能存在溢出问题。因此,在删除元素时,需要小心确保计数器不会变为负数。

这种方法允许支持删除操作,但会占用更多的内存。计数型Bloom过滤器在需要跟踪元素出现次数的应用中非常有用,但仍然需要注意误判和溢出问题

另请注意,标准Bloom过滤器无法实现可靠的删除操作,因为删除一个元素可能会影响其他元素的位状态,从而导致不可预测的行为。计数型Bloom过滤器是一种可以处理删除操作的变体,但它需要更多的内存和计算资源。
示一个元素被插入的次数。

以下是如何扩展Bloom过滤器以支持删除元素的步骤:

  1. 初始化计数型Bloom过滤器:创建一个计数型Bloom过滤器,其中每个位都初始化为0。计数型Bloom过滤器的大小通常与标准Bloom过滤器相同。
  2. 插入元素:当要插入元素时,不再将相应的位设置为1,而是增加相应的计数器。每次插入操作会增加计数器的值。
  3. 查询元素:在查询操作中,检查计数器是否大于零。如果计数器大于零,则表示元素存在。这是因为每次插入操作都会增加计数器的值。
  4. 删除元素:要删除元素,可以递减相应的计数器。如果计数器变为零,元素就被标记为不存在。

注意:在计数型Bloom过滤器中,可能存在溢出问题。因此,在删除元素时,需要小心确保计数器不会变为负数。

这种方法允许支持删除操作,但会占用更多的内存。计数型Bloom过滤器在需要跟踪元素出现次数的应用中非常有用,但仍然需要注意误判和溢出问题

另请注意,标准Bloom过滤器无法实现可靠的删除操作,因为删除一个元素可能会影响其他元素的位状态,从而导致不可预测的行为。计数型Bloom过滤器是一种可以处理删除操作的变体,但它需要更多的内存和计算资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1080968.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HarmonyOS学习 -- ArkTS开发语言入门

文章目录 一、编程语言介绍二、TypeScript基础类型1. 布尔值2. 数字3. 字符串4. 数组5. 元组6. 枚举7. unknown8. void9. null 和 undefined10. 联合类型 三、TypeScript基础知识条件语句if语句switch语句 函数定义有名函数和匿名函数可选参数剩余参数箭头函数 类1. 类的定义2.…

华为认证 | 这门HCIA认证正式发布!

华为认证云计算工程师HCIA-Cloud Computing V5.5&#xff08;中文版&#xff09;自2023年9月28日起&#xff0c;正式在中国区发布。 01 发布概述 基于“平台生态”战略&#xff0c;围绕“云-管-端”协同的新ICT技术架构&#xff0c;华为公司打造了覆盖ICT领域的认证体系&#…

机器人制作开源方案 | 齿轮传动轴偏心轮摇杆简易四足

1. 功能描述 齿轮传动轴偏心轮摇杆简易四足机器人是一种基于齿轮传动和偏心轮摇杆原理的简易四足机器人。它的设计原理通常如下&#xff1a; ① 齿轮传动&#xff1a;通过不同大小的齿轮传动&#xff0c;实现机器人四条腿的运动。通常采用轮式齿轮传动或者行星齿轮传动&#xf…

git多分支、git远程仓库、ssh方式连接远程仓库、协同开发(避免冲突)、解决协同冲突(多人在同一分支开发、 合并分支)

1 git多分支 2 git远程仓库 2.1 普通开发者&#xff0c;使用流程 3 ssh方式连接远程仓库 4 协同开发 4.1 避免冲突 4.2 协同开发 5 解决协同冲突 5.1 多人在同一分支开发 5.2 合并分支 1 git多分支 ## 命令操作分支-1 创建分支git branch dev-2 查看分支git branch-3 分…

bash一行输入,多行回显demo脚本

效果图&#xff1a; 脚本&#xff1a; #!/bin/bash # 定义一个变量&#xff0c;用来存储输入的内容 input"" # 定义一个变量&#xff0c;用来存储输入的字符 char""# 为了让read能读到空格键 IFS_store$IFS IFS# 提示内容&#xff0c;在while循环中也有&a…

SMOS数据处理,投影变换,‘EPSG:6933‘转为‘EPSG:4326‘

在处理SMOS数据时&#xff0c;遇到了读取nc数据并存为tif后&#xff0c;影像投影无法改变&#xff0c;因此全球数据无法重叠。源数据的投影为EPSG:6933&#xff0c;希望转为EPSG:4326。 解决代码。 python import os import netCDF4 as nc import numpy as np from osgeo impo…

阿里云ModelScope 是一个“模型即服务”(MaaS)平台

简介 项目地址&#xff1a;https://github.com/modelscope/modelscope/tree/master ModelScope 是一个“模型即服务”(MaaS)平台&#xff0c;旨在汇集来自AI社区的最先进的机器学习模型&#xff0c;并简化在实际应用中使用AI模型的流程。ModelScope库使开发人员能够通过丰富的…

sap 一次性供应商 供应商账户组 临时供应商 <转载>

原文链接&#xff1a;https://blog.csdn.net/xianshengsun/article/details/132620593 sap中有一次性供应商这个名词&#xff0c;一次性供应商和非一次性供应商又有什么区别呢&#xff1f; 有如何区分一次性供应商和非一次性供应商呢&#xff1f; 1 区分一次性供应商和非一次性…

狄拉克函数及其性质

狄拉克函数及其性质 狄拉克函数 近似处理 逼近近似 积分近似 狄拉克函数的性质 狄拉克函数的Hermite展开

【C++】【自用】STL六大组件:算法

文章目录 &#x1f53a;sortstable_sort&#x1f53a;reverse&#x1f53a;swap&#x1f53a;find&#x1f53a;max/min&#x1f53a;next_permutation/prev_permutation 全排列binary_searchlower_bound/upper_bound 求下界和上界set_union/set_intersection/set_difference 求…

结构体课程自我理解

目录 1. 结构体类型的声明 1.2特殊的声明方法 1.3结构体的自引用 1.4 typedef 对结构体命名 2. 结构体变量的创建和初始化 3. 结构成员访问操作符 4. 结构体内存对⻬ 4.1下面给大伙4个练习题&#xff0c;有自我解析 4.2为什么存在内存对齐 4.3修改默认对齐数 5. …

Centos中利用自带的定时器Crontab_实现mysql数据库自动备份_linux中mysql自动备份脚本---Linux运维工作笔记056

这个经常需要,怕出问题因而需要经常备份数据库,可以利用centos自带的定时器,配合脚本实现自动备份. 1.首先查看一下,这个crontab服务有没有打开: 执行:ntsysv 可以看到已经开机自启动了. 注意这个操作界面,用鼠标不行,需要用,tab按键,直接tab到确定,或取消,然后按回车回到命…

如何下不可选中的文章

背景&#xff1a; 看到了一篇比较有用的微信公众号文章&#xff08;这个文章应该是跳转到了公众号外的网站的 url 了&#xff09;&#xff0c;想留档&#xff0c;但是手机选中不了。但是这个事情作为程序员&#xff0c;怎么能束手呢。 操作&#xff1a; 1、将微信公众号链接在…

Copa:无需重建镜像,直接修补容器漏洞

关注【云原生百宝箱】公众号&#xff0c;与你一起探讨应用迁移&#xff0c;GitOps&#xff0c;二次开发&#xff0c;解决方案&#xff0c;CNCF生态。 copa 是一个使用 Go 编写的 CLI 工具&#xff0c;基于 buildkit&#xff0c;可以根据像 Trivy 这样的流行工具的漏洞扫描结果直…

LeetCode-343-整数拆分

题目描述&#xff1a; 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 题目链接&#xff1a; LeetCode-343-整数拆分 解题思路&#xff1a; 还是根据动规五…

Centos (含Rocky-Linux) VSFTPD 简单设置

本文并非深入讨论vsftp配置的文章&#xff0c;仅以能连通为目的&#xff0c;适合那些临时需要上传点东西到服务器的场景。 一、安装 dnf -y updatednf -y install vsftpdsystemctl start vsftpdsystemctl enable vsftpd二、防火墙 开放21端口&#xff1a; firewall-cmd --zo…

利用正则表达式进行数据采集和处理

目录 一、正则表达式的概述 二、正则表达式在数据采集中的运用 1、匹配和提取数据 2、数据清洗 3、数据验证 三、Python中的re模块介绍 1、re.match()方法 2、re.search()方法 总结 正则表达式是一种强大的文本处理工具&#xff0c;它可以用于模式匹配、提取、替换等操…

Android 自定义横向时间轴

示例&#xff1a; 一、添加依赖 dependencies {******//添加RecyclerView的依赖包implementation androidx.recyclerview:recyclerview:1.2.1 } 二、页面代码 activity_main.xml: <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmln…

大语言模型迎来重大突破!找到解释神经网络行为方法

前不久&#xff0c;获得亚马逊40亿美元投资的ChatGPT主要竞争对手Anthropic在官网公布了一篇名为《朝向单义性&#xff1a;通过词典学习分解语言模型》的论文&#xff0c;公布了解释经网络行为的方法。 由于神经网络是基于海量数据训练而成&#xff0c;其开发的AI模型可以生成…

Virtual Box + Vagrant 快速搭建 Linux 虚拟开发环境

Virtual Box Vagrant 快速搭建 Linux 虚拟开发环境 1、根据自己所使用的操作系统平台&#xff0c;选择下载对应的虚拟机客户端软件 Virtual Box 并进行安装&#xff0c;这里选择的是 Virtual Box 7.0.10 Windows hosts 平台安装包。 选择安装目录为其他盘&#xff0c;避免默认…