【Java】位图 布隆过滤器

位图

初识位图

位图, 实际上就是将二进制位作为哈希表的一个个哈希桶的数据结构, 由于二进制位只能表示 0 和 1, 因此通常用于表示数据是否存在.

如下图所示, 这个位图就用于标识 0 ~ 14 中有什么数字存在

在这里插入图片描述

可以看到, 我们这里相当于是把下标作为了 key-value 的一员. 但是这样同样也使得位图这个数据结构非常有局限性, 因为下标只能是整数. 因此通常来说, 位图都是用于存储整数是否存在的.

那么位图这么有局限性, 我们为何要使用它呢?

实际上, 我们可以看到, 存储一个数字是否存在, 我们只需要消耗 1 个比特位, 而如果我们使用正常的一个哈希表的键值对来存储的话, 首先是一个整型就是 4 个字节, 其次就是一个 boolean 类型, 我们暂定为 1 字节大小. 而这样相当于就占了 5 个字节, 总共为 40 个比特位.

那么结论也很明显了, 位图存储一个数据是否存在, 只需要消耗 1 比特空间, 相较于直接使用哈希表键值对存储, 大大提升了空间的使用效率.

实现思路

首先要实现位图, 我们肯定需要一个能很好操作比特位的数据类型, 那么在这一点上, 只要是整型都可以轻易的做到, 因此 byte, short, int, long 都是可行的

但是如果只使用单个数, 那肯定是不够的, 以 int 为例, 单个 int 就只有 32 位, 那假如说要存 100 个数字呢? 因此这里最好我们使用一个数组来操作. 我们这里就采用 byte 数组来实现(因为画图好画)

那么由于 byte 是 8 位, 因此我们就可以知道 byte[0] (这里没有数组名因此这样简称) 对应着的是前 8 位, 代表着 0 ~ 7 这八个数字. 而 byte[1] 则往后对应, 对应着 8 ~ 15, 后续同理… 后续我们通过使用 num / 8 就可以轻松的获取到对应下标

在这里插入图片描述

但是接下来另一个问题来了, 我们如何操作到位图中的每一个位呢? 如果是最简单的思路, 其实就是靠数字 1 来操作, 因为数字 1 的二进制位为 0000 0001 其中这个单独的 1 位置可以非常便于我们操作, 并且相较于其他的数字可读性非常的高.

那么此时我们会发现一个问题, 假如我想要令 byte[0] 中代表 0 的一位为 1, 那么此时我们还需要通过左移 1 来实现, 如下图所示

在这里插入图片描述

这样有没有问题呢? 当然没有, 不过依旧是比较的反直觉, 明明我们修改的数应该是 0 但是却反而需要通过移位来做, 因此我们这里就采用在每一个数字里面里面, 下标都是逆序的设计方式

在这里插入图片描述

可以看到此时在 byte[0] 里面, 下标就是从 7 依次减到 0, 而在 byte[1] 里面, 下标从 15 减到 8. 随后如果我们要修改 0 对应的数字, 需要左移的位就是 0 % 8, 其他的同理

当然, 这部分的设计是完全偏向于个人喜好的, 如果想要实现刚开始的顺序记录, 也不是不可以. 不过此处后续将会采用内部下标逆序的设计方式来书写代码, 感兴趣的可以自行实现顺序的版本.

位的基本操作

由于要操作位图, 相关的位操作我们必须要提前了解下, 这样才会便于我们后续代码的书写. 当然这里介绍的是具体的位相关操作, 如果对二进制位没有基本了解以及位运算也没有基本了解的, 需要自行先去了解一下相关内容

前提要求, 下面的所有第 x 位指的是从右往左, 从 0 开始来计数的

例如 0000 0001

其中 1 在第 0 位

  1. 给定一个数 n, 确定它的二进制中第 x 位是 0 还是 1

这个其实非常简单, 直接用一个 1 按位与即可, 要么把 1 左移 x 位后按位与, 要么把 n 右移 x 位后按位与

(1 << x) & n == 1 或 (n >> x) & n == 1

这个操作主要用于位图的查找功能


  1. 将一个数 n 的二进制中的第 x 位修改为 1

要把第 x 位修改为 1, 使用一个 1 左移 x 位后, 采用按位或操作即可

n |= (1 << x)

这个操作主要用于位图的记录功能


  1. 将一个数 n 的二进制中的第 x 位修改为 0

这个要修改为 0, 其实还是要用 1 左移 x 位, 不过要再取一次反, 然后采用按位与的操作

n &= (~(1 << x))

这个操作主要用于位图的删除功能

位图实现

其实目前所有的基础知识都介绍完了, 尤其是上面的三个基础操作, 接下来实现代码应该是非常简单的

初始化

public class MyBitSet {private byte[] bits;private static final int DEFAULT_SIZE = 8;public MyBitSet() {this(DEFAULT_SIZE);}public MyBitSet(int size) {bits = new byte[size / 8 + 1];}
}

存储元素

// 将某个数存入位图
public void set(int index) {int byteIndex = index / 8;// 扩容if(byteIndex >= bits.length){bits = Arrays.copyOf(bits, byteIndex + 1);}int bitIndex = index % 8;bits[byteIndex] |= (byte) (1 << bitIndex);
}

查找元素

// 查看某个数在位图中是否存在
public boolean get(int index) {int byteIndex = index / 8;int bitIndex = index % 8;return (bits[byteIndex] & (1 << bitIndex)) != 0;
}

删除元素

// 将某个数从位图中清除
public void clear(int index) {int byteIndex = index / 8;int bitIndex = index % 8;bits[byteIndex] &= (byte) ~(1 << bitIndex);
}

位图应用

位图由于其只能存储整数以及占用空间小的特性, 通常可以用于在海量的整数中去记录一个数是否存在, 另外其还可以用于做去重 + 排序的工作

public class Main {public static void main(String[] args) {int[] arr = {1, 5, 4, 7, 8, 9, 4, 22, 3, 1};MyBitSet bitSet = new MyBitSet(100);// 添加元素for (int i : arr) {bitSet.set(i);}// 遍历for (int i = 0; i < 100; i++) {if (bitSet.get(i)) {System.out.print(i + " ");}}}
}

同时它也可以实现一些基本的集合功能, 例如求交集

public BitSet and(MyBitSet other) {// 获取两个 BitSet 中较大的长度int size = Math.max(bits.length, other.bits.length) * 8;// 创建一个 BitSet 来存储结果BitSet result = new BitSet(size);for (int i = 0; i < size; i++) {// 如果两个 BitSet 的对应位置都为 1,则将结果 BitSet 的对应位置设为 1if (get(i) && other.get(i)) {result.set(i);}}return result;
}

测试代码

public class Main {public static void main(String[] args) {int[] set1 = {1, 5, 4, 7, 8, 9, 4, 22, 3, 1};int[] set2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};MyBitSet bitSet1 = new MyBitSet(100);MyBitSet bitSet2 = new MyBitSet(100);for (int i = 0; i < set1.length; i++) {bitSet1.set(set1[i]);}for (int i = 0; i < set2.length; i++) {bitSet2.set(set2[i]);}BitSet and = bitSet1.and(bitSet2);for (int i = 0; i < 100; i++) {if(and.get(i)){System.out.print(i + " ");}}}
}

BitSet 使用

Java 也提供了位图相关实现, 即 BitSet. 使用非常简单, 使用 get(), set() 方法即可

public class Main {public static void main(String[] args) {BitSet bitSet = new BitSet(8);int[] arr1 = {1, 2, 4, 5, 22, 6, 9, 4};for (int i = 0; i < arr1.length; i++) {bitSet.set(arr1[i]);}System.out.println(bitSet);}
}

布隆过滤器

初识布隆过滤器

前面我们提到如果面对着海量数据, 哈希表就会过度占用空间, 位图则只能用于处理整型. 那么有没有什么办法可以即节省空间, 又能处理海量数据呢?

此时有人可能就想了, 那为何我们不能把一个东西进行哈希, 计算出哈希值后放到对应的位图里面呢?

但是这种做法忽略了哈希表的一个重要特点, 就是会发生哈希冲突, 倘若发生哈希冲突, 那么此时位图是很难进行处理的.

其实布隆过滤器, 就相当于是我们上面这个思路的拓展, 它对一个元素, 会采用多个不同的哈希函数来进行哈希, 最后将位图中的多个位进行记录.

那此时可能有人还是要问了: 这个似乎也没有完全解决冲突问题, 那这个思路岂不是还有问题?

实际上, 布隆过滤器本身设计出来就是作为一个概率型数据结构存在的, 在判断某一个元素是否存在于容器中的时候, 会存在一定的误判率. 不过这个是可以通过调整哈希函数的个数以及容器的大小来调整误判概率的. 具体如何计算是一个数学问题, 我们这里就不多讨论了.

误判相关

实际上, 布隆过滤器的原理还是非常简单的, 无非就是用多个哈希函数去计算出下标然后丢到位图里面. 但是我们仍旧没有弄明白, 为何布隆过滤器会有误判, 误判会导致什么结果?

实际上, 为什么会有误判这个点非常好理解, 其实就是位图没法处理哈希冲突的一种体现. 我们以极端情况来看, 假设只使用一个哈希函数, 对 zhangsan 这个名字进行哈希, 得到的下标是 10, 我们将其存储到位图中.

在这里插入图片描述

随后我们尝试在里面查找 lisi 这个数据, 但是假设其计算的哈希下标也是 10, 那么此时访问 10 发现结果是 1, 就发生误判了

在这里插入图片描述

针对多个哈希函数, 其实也是同样的道理, 无非就是访问的点多了, 概率会更低而已.

那么现在我们已知了, 布隆过滤器返回 true 的时候, 是有可能误判的, 那么当其返回 false 的时候有没有可能会误判呢?

其实是不会的, 因为返回 true 会误判的本质是, 那个位置发生了哈希冲突, 且有某一个元素占用了那个位置, 那么只要有元素占用了那个位置, 就一定会返回 true. 而返回 false 则不满足有元素占用的那个条件, 因此返回 false 是不会发生误判的.

综上所述, 布隆过滤器会在返回 true 的时候概率发生误判, 而 false 则一定不会发生误判.

布隆过滤器实现

初始化

public class MyBloomFilter {// 位图private BitSet bitSet;// 标记当前容量, 主要用于给哈希 -> 下标的映射private int size;// 采用 HashMap 类似设计, 便于进行哈希 -> 下标的映射private static final int DEFAULT_SIZE = 1 << 25;// 随机种子, 用于实现不同的哈希函数, 这里是随便写的private static final int[] seeds = new int[]{5, 7, 11, 13};// 哈希函数及其实现private MyHash[] func;static class MyHash {int seed;public MyHash(int seed) {this.seed = seed;}public int hash(Object key) {int h;// 直接使用 key.hashCode() * seed 来实现简单的哈希随机函数return (key == null) ? 0 : (h = key.hashCode() * seed) ^ (h >>> 16);}}public MyBloomFilter() {bitSet = new BitSet(DEFAULT_SIZE);func = new MyHash[seeds.length];size = DEFAULT_SIZE;for (int i = 0; i < seeds.length; i++) {func[i] = new MyHash(seeds[i]);}}
}

增加和查找

非常简单的两个方法, 无非就是多次调用哈希函数并且计算下标, 最后存储/读取位图而已

public void add(Object key) {if (key == null) return;for (MyHash f : func) {bitSet.set(f.hash(key) & (size - 1));}
}public boolean contains(Object key) {if (key == null) return false;for (MyHash f : func) {if(!bitSet.get(f.hash(key) & (size - 1))) return false;}return true;
}

优缺点

优点:

  1. 添加和查询的时间复杂度低, 具体时间复杂度与哈希函数有关
  2. 不存储元素本身, 需要保密的场景下有一些优势
  3. 允许有误判率的情况下, 时间和空间效率都很高
  4. 使用相同哈希函数计算的布隆过滤器可以支持集合运算

缺点:

  1. 存在误判率(可以通过添加白名单来进行兜底, 对可能进行误判的数据进行存储)
  2. 传统的布隆过滤器不支持删除操作, 因为可能会影响其他元素. 如果需要实现可以给每一个位加一个计数器, 不过数据量过多的情况可能会导致计数器发生溢出
  3. 不能获取到元素本身

使用场景

  1. 去重场景, 例如网页爬虫去重 URL, 分布式系统日志去重等
  2. 过滤场景, 记录并且过滤垃圾邮件
  3. 解决缓存击穿问题:
    1. 背景: 系统访问缓存时, 如果发现缓存中没有相应数据, 那么就会尝试请求数据库来查找数据. 那么此时攻击者可以借助这个特性, 大量仿造不存在的数据对数据库进行攻击, 使得数据库服务器崩溃.
    2. 解决方案: 使用布隆过滤器记录下数据库中的所有数据, 经过缓存前先进行一轮判断, 发现返回 false 直接不响应即可(因为布隆过滤器返回 false 一定代表不存在). 若返回 true, 则才允许尝试访问后续的其他资源.

使用布隆过滤器

虽然 Java 没有提供官方的布隆过滤器实现, 但是第三方库还是有一些实现的, 下面我们简单使用一个

首先使用 Maven 创建项目, 并且引入下面的依赖

<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>19.0</version>
</dependency>

然后可以通过下面的代码创建一个布隆过滤器, 其中第一个参数主要是将对象转为字节数组, 提供给布隆过滤器用于进行哈希运算的, 由于我们这里只是简单使用, 因此不详细介绍了

public class Main {public static void main(String[] args) {// 期望的误判率double fpp = 0.001;// 预计要插入多少数据int size = 100000;// 创建布隆过滤器BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);}
}

接下来我们来简单测试下这个东西的误判率

public class Main {public static void main(String[] args) {// 期望的误判率double fpp = 0.001;// 预计要插入多少数据int size = 100000;// 创建布隆过滤器BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);// 存 0 ~ 100000for (int i = 0; i <= size; i++) {bloomFilter.put(i);}// 通过 100000 ~ 200000 的数据,看看有多少个是误判的int count = 0;for (int i = 100000; i <= 200000; i++){if (bloomFilter.mightContain(i)) {count++;}}System.out.println("误判率:" + count + " / " + (200000 - 100000) + " = " + (double) count / (200000 - 100000));}
}

运行可以发现误判率基本大差不差, 是符合预期的

在这里插入图片描述

海量数据处理相关问题

  1. 给一个超过 100G 大小的日志文件, 文件中存储着 IP 地址, 如何找到出现次数最多 / Top-K 的 IP 地址?

这种问题, 说是几百 G 的数据, 本质上是希望告诉你, 这个数据是无法存储到内存中的, 也就是说把这些数据直接存储到内存中是不可行的, 我们需要进行预处理.

那么针对这种大文件的处理, 核心思路无非就一个, 拆小文件, 但是这里也有两种拆法:

  1. 无脑拆分, 直接拆成能够装入内存中的大小, 随后遍历每一个文件进行全局计数.
  2. 哈希拆分, 通过对 IP 地址进行哈希操作, 保证每一个 IP 地址都能够被映射到同一个文件中. 最后分别统计出每一个文件出现次数最多的即可, 就不需要全局计数了. (假如是 Top-k 问题, 则需要对应选出每个文件 Top-k 的 IP 数, 随后进行下比较即可)

当然, 这里的哈希拆分我们需要注意, 哈希函数必须要合理, 使得每一个文件大小是均匀的, 否则可能会导致某一个文件过大无法放入内存进行处理.

  1. 给定两个文件, 分别有 100 亿个整数, 如何求其的交集, 并集, 差集?
  • 哈希拆分: 依旧是老套路, 通过哈希把相同数字塞同一个文件里面, 随后对每一个小文件进行对应集合操作
  • 位图:
    • 使用两个位图来记录数据
    • 将两个位图中的所有位进行与操作, 得到交集
    • 将两个位图中的所有位进行或操作, 得到并集
    • 将两个位图中的所有位进行异或操作, 得到差集
  1. 给定 100 亿个整数, 设计算法找出只出现过一次的整数
  • 哈希拆分思路: 依旧是通过哈希来把相同数字放到同一个文件中, 随后查看每一个小文件即可. 同样的, 这里也需要保证哈希函数设计合理, 使得每一个文件大小均匀. 这也是为什么不能采用区间划分, 因为可能某一些数字出现很多, 从而导致数字集中在一个文件里面
  • 双位图思路:

位图, 是可以用于处理海量整数数据的记录的, 但是问题就在于它只能记录一个数是否出现, 即只能表示有和无这两个信息. 而如今这个情景需要我们记录三个信息: 1. 无 2. 有一次 3. 有一次以上

那么有没有什么办法可以让位图可以多记录一些信息呢? 当然有, 一个位图不够, 那么就用两个.

此时相当于一个数对应着两个位, 此时可以表示的情况就有 00 01 10 11 四种情况了, 用于记录三种情况绰绰有余. 并且其实这两个位可以用于做二进制计数, 分别对应 0 1 2 3

  • 单位图思路:

那么继承上面的双位图思路, 我们能否使用单个位图呢? 答案当然也是可以的, 但是一个位还是不够用, 因此我们可以给每一个数字分两个位, 随后记录方式和上面一样.

  1. 给定 100 亿个整数, 设计算法找出出现次数不超过两次的整数

这个问题实际上是上面问题的拓展, 但是核心思路都是一致的

  • 哈希拆分思路: 依旧是通过哈希来把相同数字放到同一个文件中, 随后查看每一个小文件即可.
  • 位图思路: 采用两个位图/单位图的两个位, 来进行二进制计数, 记录出现次数为 01 和 10 的即可(即对应出现次数为 1 和 2 的)
  1. 给两个 100G 文件, 如何近似的求其交集?

这里由于并不要求我们精准的求出交集, 所以我们除了采用经典的哈希拆分思路还可以借助布隆过滤器来做.

思路也非常的简单, 首先把第一个文件里面的数据丢到布隆过滤器中, 随后遍历第二个文件来在布隆过滤器中查找是否存在即可.

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【工欲善其事】利用 DeepSeek 实现复杂 Git 操作:从原项目剥离出子版本树并同步到新的代码库中

文章目录 利用 DeepSeek 实现复杂 Git 操作1 背景介绍2 需求描述3 思路分析4 实现过程4.1 第一次需求确认4.2 第二次需求确认4.3 第三次需求确认4.4 V3 模型&#xff1a;中间结果的处理4.5 方案验证&#xff0c;首战告捷 5 总结复盘 利用 DeepSeek 实现复杂 Git 操作 1 背景介绍…

BGP路径属性

公认必遵循 BGP必须都能识别&#xff0c;且必须发送报文必须包含 Origin&#xff1a;起源属性&#xff0c;I,E,&#xff1f;三种&#xff0c;I是BGP通过IGP协议学到的路由&#xff08;比如ospf&#xff0c;isis&#xff0c;rip&#xff09;&#xff0c;E是从EGP协议学到的&am…

Vue 图片引用方式详解:静态资源与动态路径访问

目录 前言1. 引用 public/ 目录2. assets/ 目录3. 远程服务器4. Vue Router 动态访问5. 总结6. 扩展&#xff08;图片不显示&#xff09; 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 在 Vue 开发中&#x…

【网络编程】Java高并发IO模型深度指南:BIO、NIO、AIO核心解析与实战选型

​​ 目录 一、引言1.1 本文目标与适用场景1.2 什么是IO模型&#xff1f;阻塞 IO 模型非阻塞 IO 模型IO 多路复用模型信号驱动 IO 模型异步 IO 模型 二、基础概念解析2.1 IO模型的分类与核心思想IO模型的分类核心思想分类对比与选择依据技术示意图 2.2 同步 vs 异步 | 阻塞 vs…

基序和纯度分数的计算

以下对这两个概念的详细解释&#xff1a; 基序 纯度分数 PWM矩阵的来源 为什么会有PWM矩阵&#xff1f; 一个特定的转录因子&#xff08;TF&#xff09;的结合位点的基序&#xff08;motif&#xff09;并不是唯一的。实际上&#xff0c;TF结合位点通常具有一定的序列变异性&a…

算法日记11:SC63(离散化)

一、题目 二、题解 法一&#xff1a;前缀和&#xff08;会炸&#xff09; 对于这道题目&#xff0c;我们的第一个朴素想法就是用前缀和来进行简化操作&#xff0c;这个思路非常简单&#xff0c;就是前缀和的标准模板题&#xff0c;代码如下 void solve() {int n,q;cin>&g…

w185客户关系管理系统

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

[STM32 标准库]EXTI应用场景 功能框图 寄存器

一、EXTI 外部中断在嵌入式系统中有广泛的应用场景&#xff0c;如按钮开关控制&#xff0c;传感器触发&#xff0c;通信接口中断等。其原理都差不多&#xff0c;STM32会对外部中断引脚的边沿进行检测&#xff0c;若检测到相应的边沿会触发中断&#xff0c;在中断中做出相应的处…

Windows下怎么安装FFFmpeg呢?

在Windows下使用Open-webui报错&#xff0c;说Couldnt find ffmpeg or avconv,解决open-webui报错Couldn‘t find ffmpeg or avconv-CSDN博客于是尝试解决问题&#xff0c;那么Windows下怎么安装FFFmpeg呢&#xff1f; 尝试了两种方法。 第一种方法pip安装&#xff08;失败&…

Hive on Spark优化

文章目录 第1章集群环境概述1.1 集群配置概述1.2 集群规划概述 第2章 Yarn配置2.1 Yarn配置说明2.2 Yarn配置实操 第3章 Spark配置3.1 Executor配置说明3.1.1 Executor CPU核数配置3.1.2 Executor内存配置3.1.3 Executor个数配置 3.2 Driver配置说明3.3 Spark配置实操 第4章 Hi…

【OMCI实践】ONT上线过程的omci消息(三)

引言 在上一篇文章【OMCI实践】ONT上线过程的omci消息&#xff08;二&#xff09;-CSDN博客中&#xff0c;主要介绍了ONT上线过程的OMCI交互的第一个阶段和第二个阶段omci消息&#xff0c;本篇介绍第二个阶段剩余的OMCI消息涉及到的受管实体&#xff08;ME&#xff09;的属性。…

保姆级教程Docker部署Zookeeper官方镜像

目录 1、安装Docker及可视化工具 2、创建挂载目录 3、运行Zookeeper容器 4、Compose运行Zookeeper容器 5、查看Zookeeper运行状态 6、验证Zookeeper是否正常运行 1、安装Docker及可视化工具 Docker及可视化工具的安装可参考&#xff1a;Ubuntu上安装 Docker及可视化管理…

【数据结构】栈与队列

栈 栈的概念及结构 栈:一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。 压栈:栈的插入操作叫做进栈/压栈/入栈&…

安全实验作业

一 拓扑图 二 要求 1、R4为ISP&#xff0c;其上只能配置IP地址&#xff1b;R4与其他所有直连设备间均使用共有IP 2、R3-R5-R6-R7为MGRE环境&#xff0c;R3为中心站点&#xff1b; 3、整个OSPF环境IP基于172.16.0.0/16划分&#xff1b; 4、所有设备均可访问R4的环回&#x…

e2studio开发RA4M2(6)----GPIO外部中断(IRQ)配置

e2studio开发RA4M2.6--GPIO外部中断&#xff08;IRQ&#xff09;配置 概述视频教学样品申请硬件准备参考程序源码下载新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置SWD调试口设置GPIO口配置按键中断配置中断回调函数主程序 概述 GPIO&#xff08;通用输入/输出&a…

排序算法--快速排序

快速排序是高效的排序算法&#xff0c;平均时间复杂度为 O(nlog⁡n)&#xff0c;适合大规模数据排序。 1.挖坑法 2左右指针法 3.前后指针法 // 交换两个元素的值 void swap(int* a, int* b) {int temp *a;*a *b;*b temp; }// 分区函数&#xff0c;返回分区点的索引 int par…

分享|LLM通过D-E-P-S完成长时间与多步骤的任务

《Describe, Explain, Plan and Select: Interactive Planning with Large Language Models Enables Open-World Multi-Task Agents&#xff1f; 描述、解释、计划和选择&#xff1a;使用大型语言模型进行交互式规划&#xff0c;实现开放世界的多任务代理 问题背景&#xff1a;…

chrome浏览器chromedriver下载

chromedriver 下载地址 https://googlechromelabs.github.io/chrome-for-testing/ 上面的链接有和当前发布的chrome浏览器版本相近的chromedriver 实际使用感受 chrome浏览器会自动更新&#xff0c;可以去下载最新的chromedriver使用&#xff0c;自动化中使用新的chromedr…

swagger使用指引

1.swagger介绍 在前后端分离开发中通常由后端程序员设计接口&#xff0c;完成后需要编写接口文档&#xff0c;最后将文档交给前端工程师&#xff0c;前端工程师参考文档进行开发。 可以通过一些工具快速生成接口文档 &#xff0c;本项目通过Swagger生成接口在线文档 。 什么…

一文速览DeepSeek-R1的本地部署——可联网、可实现本地知识库问答:包括671B满血版和各个蒸馏版的部署

前言 自从deepseek R1发布之后「详见《一文速览DeepSeek R1&#xff1a;如何通过纯RL训练大模型的推理能力以比肩甚至超越OpenAI o1(含Kimi K1.5的解读)》」&#xff0c;deepseek便爆火 爆火以后便应了“人红是非多”那句话&#xff0c;不但遭受各种大规模攻击&#xff0c;即便…