【算法系列 | 7】深入解析查找算法之—布隆过滤器

news2024/11/29 8:50:41

 

序言

心若有阳光,你便会看见这个世界有那么多美好值得期待和向往。

决定开一个算法专栏,希望能帮助大家很好的了解算法。主要深入解析每个算法,从概念到示例。

我们一起努力,成为更好的自己!

今天第3讲,讲一下排序算法的选择排序(Selection Sort)

1 基础介绍

查找算法是很常见的一类问题,主要是将一组数据按照某种规则进行排序。

以下是一些常见的查找算法及其应用场景:

  1. 布隆过滤器(Bloom Filter):适用于判断一个元素是否存在于一个大规模的数据集中,时间复杂度为O(1),但有一定的误判率。
  2. 二分查找(Binary Search):适用于有序数组中查找元素,时间复杂度为O(log n);
  3. 哈希表查找(Hash Table:适用于快速查找和插入元素,时间复杂度为O(1),但需要额外的存储空间;
  4. 线性查找(Linear Search):适用于无序数组中查找元素,时间复杂度为O(n);
  5. 插值查找(Interpolation Search):适用于有序数组中查找元素,时间复杂度为O(log log n),但是对于分布不均匀的数据集效果不佳;
  6. 斐波那契查找(Fibonacci Search):适用于有序数组中查找元素,时间复杂度为O(log n),但需要额外的存储空间;
  7. 树表查找(Tree Search):适用于快速查找和插入元素,时间复杂度为O(log n),但需要额外的存储空间;
  8. B树查找(B-Tree):适用于大规模数据存储和查找,时间复杂度为O(log n),但需要额外的存储空间;

一、布隆过滤器介绍

1.1 原理介绍

布隆过滤器(Bloom Filter)是一种概率型数据结构,用于快速判断一个元素是否可能存在于一个集合中,同时具有高效的插入和查询操作。它的原理基于位数组和哈希函数。

布隆过滤器的核心是一个位数组(bit array)或称为位向量(bit vector),用于表示元素的存在状态。初始时,所有位都被置为0。

布隆过滤器使用一系列不同的哈希函数(hash functions),这些哈希函数将输入的元素映射为位数组中的不同位置。哈希函数的数量和定义根据具体的应用场景而定,通常会选择一些独立的哈希函数。

插入元素时,将元素经过哈希函数的映射,得到一系列位数组中的位置,然后将这些位置的位设置为1,表示对应的元素存在于布隆过滤器中。

查询元素时,将要查询的元素经过哈希函数的映射,得到一系列位数组中的位置,然后检查这些位置的位是否都为1。

如果有任何一个位置的位为0,则可以确定该元素一定不存在于布隆过滤器中;如果所有位置的位都为1,则该元素可能存在于布隆过滤器中,但不是确定存在。

因此,布隆过滤器的查询结果可能会有误判,即将不存在的元素误判为存在,但不会将存在的元素误判为不存在。

优点

由于布隆过滤器使用位数组和哈希函数,具有以下特点:

  1. 空间效率高:布隆过滤器只需使用位数组存储元素的存在状态,不需要存储元素本身,因此占用的空间相对较小。

  2. 查询效率高:布隆过滤器查询的时间复杂度是常数级别的,与集合中元素的数量无关。

  3. 插入效率高:布隆过滤器插入的时间复杂度也是常数级别的。

缺点

然而,布隆过滤器也存在一些缺点:

  1. 误判率(False Positive):布隆过滤器的查询结果可能会有误判,将不存在的元素误判为存在。

  2. 不支持元素的删除:布隆过滤器的设计目标是快速判断元素的存在性,不支持元素的删除操作。

  3. 难以扩展:一旦布隆过滤器被创建,就很难动态地调整其大小。

总的来说,布隆过滤器适用于对查询效率要求较高、可以容忍一定的误判率以及元素不经常变动的场景,如缓存、防止缓存击穿、爬虫的去重等应用。

图解原理

以下是一个简单的图解布隆过滤器的示意图:

图表示一个初始状态的布隆过滤器,位数组中的所有位都被初始化为0。

 

接下来,我们插入一个元素 "apple" 和 "orange",假设我们选择了三个哈希函数,并且得到的哈希值分别为 1、5 和 7。我们将对应的位设置为1,表示这些位置上的元素存在。

 现在,我们查询元素 "apple" 和 "banana"。根据哈希函数得到的位位置分别为 1、4 和 7。我们检查这些位置上的位,如果其中有任何一个位为0,则可以确定该元素不存在于布隆过滤器中;如果所有位置上的位都为1,则该元素可能存在于布隆过滤器中。

根据图示,我们可以确定 "apple" 可能存在于布隆过滤器中,因为对应的位置上的位都为1。而 "banana" 可能不存在于布隆过滤器中,因为其中一个位置上的位为0。

这就是布隆过滤器的基本原理。

通过使用位数组和哈希函数,布隆过滤器可以快速判断一个元素是否可能存在于一个集合中,具有高效的插入和查询操作。

1.2 复杂度 

布隆过滤器的时间和空间复杂度如下:

时间复杂度:

  • 插入操作的时间复杂度是O(k),其中k是哈希函数的数量。
  • 查询操作的时间复杂度也是O(k)。

空间复杂度:

  • 布隆过滤器的空间复杂度主要取决于位数组的大小和哈希函数的数量。通常情况下,位数组的大小取决于预期的元素数量和期望的误判率。位数组的大小会随着元素数量的增加而增加,以及期望的误判率的降低而增加。

1.3使用场景

布隆过滤器适用于以下场景:

  1. 数据量大,但内存有限:由于布隆过滤器只需要使用位数组来表示元素存在状态,相对于其他数据结构,它具有较小的内存占用。这使得它在内存有限的情况下能够存储大量的元素。

  2. 快速判断元素是否存在:布隆过滤器可以在常数时间内判断一个元素是否可能存在于集合中,无需实际存储元素本身,这使得它具有非常高的查询效率。它可以用于加速对大型数据集或数据库的查询操作。

  3. 容忍一定的误判率:布隆过滤器的查询结果可能会有误判,将不存在的元素误判为存在(即假阳性)。因此,它适用于那些可以容忍一定误判率的应用场景。例如,网页爬虫可以使用布隆过滤器来去重,避免重复爬取相同的网页;缓存系统可以使用布隆过滤器来判断某个数据是否已经缓存,从而避免无谓的IO操作。

  4. 不需要删除操作:布隆过滤器不支持元素的删除操作。一旦元素被插入到布隆过滤器中,就无法删除。因此,它适用于那些不需要频繁删除元素的场景。

需要注意的是,布隆过滤器在某些情况下可能会出现误判,将不存在的元素误判为存在。因此,在一些对准确性要求很高的场景下,布隆过滤器可能不适用。

二、代码实现

2.1 Python 实现

代码示例

import math
import mmh3
from bitarray import bitarray

class BloomFilter:
    def __init__(self, num_items, false_positive_rate):
        self.num_items = num_items
        self.false_positive_rate = false_positive_rate
        self.bit_array_size = self.calculate_bit_array_size(num_items, false_positive_rate)
        self.num_hash_functions = self.calculate_num_hash_functions(self.bit_array_size, num_items)
        self.bit_array = bitarray(self.bit_array_size)
        self.bit_array.setall(0)

    def calculate_bit_array_size(self, num_items, false_positive_rate):
        numerator = num_items * math.log(false_positive_rate)
        denominator = math.log(2) ** 2
        return int(-(numerator / denominator))

    def calculate_num_hash_functions(self, bit_array_size, num_items):
        numerator = (bit_array_size / num_items) * math.log(2)
        return int(numerator)

    def add(self, item):
        for seed in range(self.num_hash_functions):
            index = mmh3.hash(item, seed) % self.bit_array_size
            self.bit_array[index] = 1

    def contains(self, item):
        for seed in range(self.num_hash_functions):
            index = mmh3.hash(item, seed) % self.bit_array_size
            if self.bit_array[index] == 0:
                return False
        return True

代码讲解

现在逐行解释代码的各个部分:

  1. 导入所需的模块:代码使用了 math 模块来进行数学计算,mmh3 模块用于实现哈希函数,bitarray 模块用于表示位数组。

  2. BloomFilter 类的初始化方法:在初始化过程中,我们需要指定预期的元素数量 num_items 和期望的误判率 false_positive_rate。根据这两个参数,我们通过调用 calculate_bit_array_size 和 calculate_num_hash_functions 方法来计算位数组的大小和哈希函数的数量。然后,我们创建一个位数组 bit_array,并将所有位初始化为0。

  3. calculate_bit_array_size 方法:根据预期元素数量和期望的误判率,使用公式 -num_items * log(false_positive_rate) / (log(2) ** 2) 计算位数组的大小,并将结果转换为整数。

  4. calculate_num_hash_functions 方法:根据位数组的大小和预期元素数量,使用公式 (bit_array_size / num_items) * log(2) 计算哈希函数的数量,并将结果转换为整数。

  5. add 方法:用于向布隆过滤器中添加元素。对于每个元素,我们使用不同的种子值(从0到num_hash_functions-1)来计算哈希值,并将对应的位数组位置设置为1。

  6. contains 方法:用于检查元素是否存在于布隆过滤器中。对于每个元素,我们使用与添加操作相同的种子值来计算哈希值,并检查对应的位数组位置。如果任何一个位置上的位为0,则可以确定元素不存在于布隆过滤器中;否则,我们认为元素可能存在于布隆过滤器中。

这就是一个简单的布隆过滤器的 Python 实现。你可以根据自己的需求进行调整和扩展。请注意,这个实现中并没有考虑动态调整布隆过滤器大小或删除元素的功能。

测试代码

import random

def generate_random_string(length):
    letters = "abcdefghijklmnopqrstuvwxyz"
    return ''.join(random.choice(letters) for _ in range(length))

num_items = 1000
false_positive_rate = 0.01

bloom_filter = BloomFilter(num_items, false_positive_rate)

# 添加元素
for _ in range(num_items):
    item = generate_random_string(10)
    bloom_filter.add(item)

# 检查元素是否存在
positive_count = 0
for _ in range(1000):
    item = generate_random_string(10)
    if bloom_filter.contains(item):
        positive_count += 1

false_positive_rate_actual = positive_count / 1000
print("实际误判率:", false_positive_rate_actual)

我们首先定义了预期的元素数量 num_items 和期望的误判率 false_positive_rate。然后,我们创建了一个 BloomFilter 实例并使用 add 方法向布隆过滤器中添加了随机生成的元素。

接下来,我们使用 contains 方法进行随机测试。我们随机生成了1000个字符串,并检查它们是否存在于布隆过滤器中。我们计算了实际的误判率,即在这1000个随机字符串中错误判断为存在的比例,并将其打印出来。

测试结果会输出一个实际的误判率,应该接近于设定的期望误判率。

请注意,由于布隆过滤器的特性,即使在没有添加的情况下,也可能会有一定的误判率。因此,实际误判率可能略高于设定的期望误判率。

运行结果

实际误判率: 0.009

2.2Java实现

代码示例

import java.util.BitSet;
import java.util.Random;

public class BloomFilter {
    private BitSet bitSet;
    private int size;
    private int numHashFunctions;
    private Random random;

    public BloomFilter(int expectedNumItems, double falsePositiveRate) {
        size = calculateBitSetSize(expectedNumItems, falsePositiveRate);
        numHashFunctions = calculateNumHashFunctions(size, expectedNumItems);
        bitSet = new BitSet(size);
        random = new Random();
    }

    public void add(String item) {
        for (int i = 0; i < numHashFunctions; i++) {
            int hash = hash(item, i);
            bitSet.set(hash, true);
        }
    }

    public boolean contains(String item) {
        for (int i = 0; i < numHashFunctions; i++) {
            int hash = hash(item, i);
            if (!bitSet.get(hash)) {
                return false;
            }
        }
        return true;
    }

    private int hash(String item, int seed) {
        random.setSeed(seed);
        return Math.abs(random.nextInt()) % size;
    }

    private int calculateBitSetSize(int expectedNumItems, double falsePositiveRate) {
        int size = (int) Math.ceil((expectedNumItems * Math.log(falsePositiveRate)) / Math.log(1.0 / (Math.pow(2.0, Math.log(2.0)))));
        return size;
    }

    private int calculateNumHashFunctions(int size, int expectedNumItems) {
        int numHashes = (int) Math.ceil((size / expectedNumItems) * Math.log(2.0));
        return numHashes;
    }
}

代码讲解

解释一下以上代码的各个部分:

  • BloomFilter 类的构造函数:在构造函数中,我们传入预期的元素数量 expectedNumItems 和期望的误判率 falsePositiveRate。然后,我们使用 calculateBitSetSize 和 calculateNumHashFunctions 方法计算位集合的大小和哈希函数的数量。接着,我们创建一个位集合 bitSet,并初始化一个 Random 对象。

  • add 方法:用于向布隆过滤器中添加元素。对于每个元素,我们使用不同的种子值(从0到numHashFunctions-1)来计算哈希值,并将对应的位设置为1。

  • contains 方法:用于检查元素是否存在于布隆过滤器中。对于每个元素,我们使用与添加操作相同的种子值来计算哈希值,并检查对应的位是否为1。如果任何一个位为0,则可以确定元素不存在于布隆过滤器中;否则,我们认为元素可能存在于布隆过滤器中。

  • hash 方法:用于计算哈希值。我们使用种子值作为随机数种子,并使用 Random 对象生成一个哈希值。然后,我们对哈希值取绝对值,并对位集合大小取模,以确保哈希值在位集合范围内。

  • calculateBitSetSize 方法:根据预期元素数量和期望的误判率,使用公式 (expectedNumItems * log(falsePositiveRate)) / log(1.0 / (Math.pow(2.0, Math.log(2.0)))) 计算位集合的大小,并返回结果。

  • calculateNumHashFunctions 方法:根据位集合的大小和预期元素数量,使用公式 (size / expectedNumItems) * log(2.0) 计算哈希函数的数量,并返回结果。

这是一个简单的布隆过滤器的 Java 实现。你可以根据自己的需求进行调整和扩展。注意,这个实现中没有考虑动态调整布隆过滤器大小或删除元素的功能。

测试代码

public class Main {
    public static void main(String[] args) {
        int expectedNumItems = 1000;
        double falsePositiveRate = 0.01;

        BloomFilter bloomFilter = new BloomFilter(expectedNumItems, falsePositiveRate);

        // 添加元素
        bloomFilter.add("apple");
        bloomFilter.add("banana");
        bloomFilter.add("orange");

        // 检查元素是否存在
        System.out.println(bloomFilter.contains("apple"));   // 输出: true
        System.out.println(bloomFilter.contains("banana"));  // 输出: true
        System.out.println(bloomFilter.contains("orange"));  // 输出: true
        System.out.println(bloomFilter.contains("grape"));   // 输出: false
        System.out.println(bloomFilter.contains("melon"));   // 输出: false
    }
}

运行结果

true
true
true
false
false

三、图书推荐

图书名称:

  • 《漫画算法:小灰的算法之旅》

图书介绍

 

本书是《漫画算法:小灰的算法之旅》的续作,通过主人公小灰的心路历程,用漫画的形式讲述了多个数据结构、算法及复杂多变的算法面试题目。

第1章介绍了几种典型的排序算法,包括选择排序、插入排序、希尔排序、归并排序、基数排序。

第2章介绍了“树”结构的高级应用,包括二叉查找树、AVL树、红黑树、B树和B+树。

第3章介绍了“图”结构的概念,以及深度优先遍历、广度优先遍历、单源最短路径、多源最短路径算法。

第4章介绍了“查找”相关的算法和数据结构,包括二分查找算法、RK算法、KMP算法,以及“跳表”这种用于高效查找的数据结构。

第5章介绍了多种职场上流行的算法面试题目及详细的解题思路,例如螺旋遍历二维数组、寻找数组中第k大元素、求股票交易的更大收益等。

等不及的小伙伴,可以点击下方链接,先睹为快:漫画算法2

 参与方式 

图书数量:本次送出 4 本   !!!⭐️⭐️⭐️
活动时间:截止到 2023-08-11 12:00:00

抽奖方式:

  • 评论区随机抽取小伙伴!

留言内容,以下方式都可以:

  • 根据文章内容进行高质量评论

参与方式:关注博主、点赞、收藏,评论区留言 

中奖名单 

🍓🍓 获奖名单🍓🍓

 中奖名单:请关注博主动态

名单公布时间:2023-08-11 下午

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

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

相关文章

计算机网络—TCP和UDP、输入url之后显示主页过程、TCP三次握手和四次挥手

TCP基本认识 TCP是面向连接的、可靠的&#xff0c;基于字节流的传输层通信协议。 图片来源小林coding 序号&#xff1a;传输方向上字节流的字节编号。初始时序号会被设置一个随机的初始值&#xff08;ISN&#xff09;&#xff0c;之后每次发送数据时&#xff0c;序号值 ISN…

合规管理,企业生存之本!这4大方法,助你规避风险

当下,合规管理已成为企业必修的一门学问。无论是上市公司还是民营企业,都面临着日益严苛的监管合规要求。然而,许多企业在在应对频繁更新的合规要求时,仍然手忙脚乱,合规工作参差不齐。 专家分析认为,企业合规困境的主要症结在于,业务运转过程中产生了大量证明文件,但企业对其…

全链路仿真压测系统

目录 1.项目背景 2.仿真压测系统成型之路 3. 818台网实战开始 4. 总结 1.项目背景 目前常用的压测工具一般都是针对QPS这一个单一指标进行考量。即使支持编写脚本的工具也只是通过参数化模拟用户。但是实际用户是使用单独设备请求服务器&#xff0c;即一个用户就是一个tc…

什么是重定向,怎么解决重定向问题

目录 引言 什么是重定向 为什么会发生重定向 常用的重定向方法 解决重定向问题的方法 代码示例 注意事项 总结 引言 在网络应用中&#xff0c;重定向是一种常见的技术&#xff0c;用于在浏览器请求页面时将用户重定向到另一个页面。本文将介绍重定向的概念&#xff0c…

Nginx反向代理配置+负载均衡集群部署

文章目录 负载均衡反向代理基础环境部署&#xff1a;什么是代理实验环境图流量过程 环境部署准备两台Web服务器安装Nginx准备页面内容添加主机名 代理服务器配置 修改windos hosts文件测试&#xff1a;终端浏览器 负载均衡反向代理基础环境部署&#xff1a; 什么是代理 正向代…

16-3_Qt 5.9 C++开发指南_使用QStyle 设置界面外观_实现不同系统下的界面效果的匹配

文章目录 1. QStyle的作用&#xff08;实现不同系统下的界面效果的匹配&#xff09;2. Qt内置样式的使用3. 源码3.1 可视化UI设计3.2 mainwindow.cpp 1. QStyle的作用&#xff08;实现不同系统下的界面效果的匹配&#xff09; Qt 是一个跨平台的类库&#xff0c;相同的界面组件…

D455+VINS-Fusion+surfelmapping 稠密建图(三)

继续&#xff0c;由surfelmapping建立的点云生成octomap八叉树栅格地图 一、安装OctomapServer 建图包 安装插件 sudo apt-get install ros-melodic-octomap-ros sudo apt-get install ros-melodic-octomap-msgs sudo apt-get install ros-melodic-octomap-server sudo apt-…

js-1:JavaScript中的数据类型?存储上有啥差别

1、在JavaScript中&#xff0c;共有两种数据类型。 基本类型 复杂类型 区别在于&#xff1a;存储位置不同 2、基本类型 主要分为以下6种&#xff1a; Number String Boolean Undefined null Symbol Number : 数值最常见的证书类型格式则为十进制&#xff0c;还可以设置八进制&…

K8s中的PV和PVC和监控

1.PV和PVC PV&#xff1a;持久化存储&#xff0c;对存储资源进行抽象&#xff0c;对外提供可以调用的地方&#xff08;类似&#xff1a;生产者&#xff09; PVC&#xff1a;用于调用&#xff0c;不需要关心内部实现细节&#xff08;类似&#xff1a;消费者&#xff09; 2.实…

【Gitee的使用】Gitee的简单使用,查看/创建SSH公匙、创建版本库、拉取代码、提交代码

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 本篇文章简单介绍&#xff0c;如何在Gitee上面创建版本库、拉取…

搭建企业级BI系统有多快?奥威BI表现惊人

搭建BI系统需要建立一个可靠的数据仓库和针对分析工具/UI的数据结构。同时&#xff0c;这个过程需要不断地迭代优化与维护。主要有搭建环境、建设数仓和分析模型、设计数据可视化报表。 不难看出&#xff0c;从零开始搭建企业级BI系统是一个工作量大、程序繁琐的工程&#xff…

Jenkins 使用

Jenkins 使用 文章目录 Jenkins 使用一、jenkins 任务执行二、 Jenkins 连接gitee三、Jenkins 部署静态网站 一、jenkins 任务执行 jenkins 创建 job job的名字最好是有意义的 restart_web_backend restart_web_mysql[rootjenkins ~]# ls /var/lib/jenkins/ config.xml …

AIGC+低代码:征途漫漫,道阻且长

人们总是高估一项科技所带来的短期效益&#xff0c;却又低估它的长期影响。 一、低代码与 AI 深度融合是未来发展趋势 2023 年上半年&#xff0c;随着 Chat GPT 的爆火&#xff0c;AI 在自然语言对话和代码领域的能力引发全世界的关注&#xff0c;现阶段的 AI 真的能将大家带入…

网络编程——MAC地址、IP地址和子网掩码

MAC地址、IP地址和子网掩码 一、MAC地址&#xff1a;硬件身份证 1、MAC地址的概念 MAC地址&#xff0c;即媒体访问控制地址&#xff08;Media Access Control Address&#xff09;&#xff0c;是一个用于唯一标识网络设备的物理地址。每个网络接口卡&#xff08;NIC&#xf…

I帧、P帧、B帧、GOP、IDR 和PTS, DTS之间的关系

一.视频传输原理 视频是利用人眼视觉暂留的原理&#xff0c;通过播放一系列的图片&#xff0c;使人眼产生运动的感觉。单纯传输视频画面&#xff0c;视频量非常大&#xff0c;对现有的网络和存储来说是不可接受的。为了能够使视频便于传输和存储&#xff0c;人们发现视频有大量…

用P2PNet进行大豆计数

文章目录 介绍在大豆数据集上可视化结果环境准备数据集结构数据链接模型训练模型推理代码介绍 这个仓库包含了P2PNet(Rethinking Counting and Localization in Crowds: A Purely Point-Based Framework)在大豆数据集上的pytorch实现。 在大豆数据集上可视化结果 环境准备 …

虚继承中对象占用的内存空间

1、虚继承中对象占用的内存空间1 #include <iostream> using namespace std;class AA {void show() {}int max(int a, int b) { return a > b ? a : b; } }; //函数并不占用内存空间class A {}; //占位符class B {int c; }; //含有一个int型数据成员class C :vi…

如何使用本地mock数据

当后端同事接口数据还未完成&#xff0c;我们前端开发需要使用数据时&#xff0c;怎么办呢&#xff1f;这里可以自己本地mock数据先用着啦&#xff01;仅在开发时使用 1. 创建一个 xxx.js文件&#xff0c;对外暴露一个数组&#xff1b; 对新建js文件编写导出&#xff0c;返回数…

创建线程、线程的挂起与恢复、线程的优先级与终止线程

目录 一、创建线程 CreateThread函数&#xff1a; 下面是示例&#xff1a; ​编辑 ThreadProc函数解释&#xff1a; DWORD的本质是 unsigned long PVOID的本质是 void* 二、线程的终止 1.WaitForSingleObject()函数&#xff1a; 示例如下&#xff1a; 2.ExitThread()函…

ChatGPT 作为 Python 编程助手

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可编辑的3D应用场景 简单的数据处理脚本 我认为一个好的起点是某种数据处理脚本。由于我打算让 ChatGPT 之后使用各种 Python 库编写一些机器学习脚本&#xff0c;这似乎是一个合理的起点。 目标 首先&#xff0c;我想尝试…