哈希的应用——布隆过滤器

news2024/11/27 12:37:08

文章目录

  • 前言
  • 1. 布隆过滤器提出
  • 2. 布隆过滤器概念
  • 3. 布隆过滤器的插入
    • 多哈希函数映射减少冲突
    • 结构定义及set(插入)函数实现
  • 4. 布隆过滤器的查找
    • test(查找)函数实现
    • 布隆过滤器允许误判
  • 5. 布隆过滤器的适用场景
  • 6. 如何选择布隆过滤器的长度和哈希函数的个数
  • 7. 测试
  • 8. 布隆过滤器删除(reset)的思考
  • 9. 布隆过滤器的优缺点分析
    • 布隆过滤器的优点
    • 布隆过滤器的缺陷
  • 10. 源码
    • bitset.h
    • BloomFilter.h
    • Test.cpp

前言

上一篇文章,我们学习了位图,位图在某些场景下是非常适用的,非常快捷方便。
但是,在文章的最后,我们也提出了位图的一些缺陷——比如位图只能映射整型数据,其它类型的数据则不行。
因为位图里面的元素去映射的其实就是下标嘛,而下标的话都是整型啊。

那有没有什么 办法可以解决呢?
这就是我们今天要学的布隆过滤器(Bloom Filter)
在这里插入图片描述

1. 布隆过滤器提出

我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的?
其实就是用服务器记录了用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。

那这就涉及到一个问题:面对海量的数据,如何进行快速的查找筛选呢?

1. 用哈希表或红黑树存储用户记录,缺点:空间问题,因为它们除了存储数据之外还有额外存储一些指针,结点颜色这些东西,而且数据量过大的时候可能直接就存不下了。
2. 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了。(这也是我们上面提到的问题)
3. 就是我们这篇文章要重点学的——将哈希与位图结合,即布隆过滤器(不仅可以提升查询效率,也可以节省大量的内存空间)

2. 布隆过滤器概念

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”(允许误判),它是用多个哈希函数,将一个数据映射到位图结构中的多个位置(即它的底层还是位图)。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
在这里插入图片描述

那接下来我们就来详细讲解一下布隆过滤器

3. 布隆过滤器的插入

上面提到布隆过滤器其实就是用哈希函数把数据映射到位图结构中。

现在有这样一个位图结构:

在这里插入图片描述

例如现在我们要插入一些元素——“百度”、“美团”、“Google”,一些字符串,那字符串没法直接映射到位图中,怎么办?

那这没什么难的,我们直接玩过的东西,可以搞一个仿函数把字符串转成整型,然后就可以往位图里面映射了。

那转成整型之后确实可以映射了,但是有没有存在什么问题呢?

是不是会存在冲突啊。
我们的位图之所以没有考虑冲突的问题因为我们说了位图适用的是海量数据,数据无重复的场景,而且位图是一种直接定址的映射。通常是用来判断某个数据存不存在的。

那我们可以使用字符串哈希等一些方法减少冲突,当然不能完全避免

而且字符串往整型的映射本身就是一个大范围到小范围的映射。
就比如一个长度为10的字符串,大家算一下有多少种?
char有256种取值,那就是256的10次方,而整形只有2^32个。
而且这还只是长度为10的一种情况,那…

多哈希函数映射减少冲突

那布隆过滤器呢采用这样一种方法来进一步的减少冲突:

比如现在我们插入了3个值
在这里插入图片描述
这时还没有发生冲突,然后再插入一个值
在这里插入图片描述
这时候B站就和美团发生了冲突。
那布隆就想到了这样一个方法来降低冲突:
既然一个值映射一个位置容易发生冲突,那我就用多个哈希函数让一个值同时映射到多个位置,就可以再进一步减少冲突。(因为如果一个元素映射多个位置的话那就需要这多个位置同时被多个元素映射才算冲突)

比如现在我们让每个插入的元素映射2个位置:

那怎么做到映射多个位置呢?
很简单,让一个元素分别通过多个哈希函数计算映射地址就行了,然后将这个多个结果对应的位置都置成1。
这样只有这多个位置都为1,才算这个元素是存在的。
在这里插入图片描述
大家看,现在我们让每个元素映射两个位置,这样的话即使某些元素的其中一个映射位置与别人发生了冲突,但是也没有构成冲突。
这样的话冲突的概率就会更小一点。

结构定义及set(插入)函数实现

先来定义一下布隆过滤器的结构:

在这里插入图片描述
这里我们给3个哈希函数,实际应用中看具体情况。N代表插入的数据个数。

那我们来写一下set:

那set的话就是用三个哈希函数计算出来三个映射地址,然后把这三个比特位都置成1
在这里插入图片描述

那我们可以找三个字符串哈希用一下(大家可以自己去网上查找)

struct BKDRHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash += ch;
			hash *= 31;
		}

		return hash;
	}
};

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


struct DJBHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};

然后模板参数的地方,我们可以直接给缺省值:

在这里插入图片描述
K我们默认给一个string类型,布隆过滤器面对的场景大多都是字符串。

4. 布隆过滤器的查找

test(查找)函数实现

那我们查找的时候如何判断一个元素在不在呢?

那其实就是去判断它映射的位置是否都置成了1就行了。
如果全部为1,就是在,如果有一个不为1,就是不存在。
在这里插入图片描述

布隆过滤器允许误判

那这里我要问大家一个问题:判断在和不在那种情况会存在误判?

🆗,要告诉大家的是,对于布隆过滤器来说:
判断在是可能不准确的,可能会误判;而判断不在是一定准确的。

为什么呢?

如果是不在的话,那么只要有一个映射的位置为0,那他就一定不在,这是不会出错的。
而判断在的话,就可能出现这样的情况:
在这里插入图片描述
大家看,上面的4个字符串是已经插入到布隆过滤器里面的值,已经把它们映射的位置都设置成了1.
现在有一个待插入元素“腾讯”,还没有插入,但是它映射的几个位置已经被之前插入的其它元素设置为1了。
那这时我们去查找“腾讯”的话,实际它是没有插入的,但是test的时候,会发现它映射的几个位置都已经被set成了1,那这时候就会误判“腾讯”是存在的。

总结一下:

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

5. 布隆过滤器的适用场景

那正由于会出现上面误判情况的原因,所以布隆过滤器的适用场景是有限的,即它适用于一些允许误判的场景

比如:

我们下载一个游戏,比如说王者荣耀,然后我们注册一个新账号的时候要给自己起一个昵称,或者使用什么改名卡修改昵称的时候
可能会出现这样的场景
在这里插入图片描述
我们输入一个昵称之后,系统提示你这个昵称已经存在了,让你换一个。
那这种情况就是允许你误判的,你输入一个昵称之后,系统提示已经存在,那这里就有两种情况:
第一种就是真的已经被用过了;
第二种就是实际没有被使用过,但是它误判了。
那这种情况即使它误判了,其实也没什么影响,因为对于用户来说,他也不知道自己输入的昵称到底有没有被用过,系统提示被用过了,那用户就会认为真的被用过了(即使是误判了),就再换一个。

那这种场景用布隆过滤器其实就挺合适的:

假如这个游戏现在有10亿用户,这些用户的数据(可能包括昵称、账号、密码这些东西)存储在数据库里面,数据库通常存在磁盘上。
那我们去查找判断的时候为什么不直接去数据库查找呢?
因为太慢了,效率太低。
所以就可以这样做:
在这里插入图片描述
我们就把所有的昵称存到布隆过滤器里面,然后用户注册新的昵称或者修改昵称的时候,就可以快速的反馈给用户昵称是否存在。

那大家想手机号码这样的信息能不能也存到布隆里面?

如果是手机号码的话有没有注册过用户自己是不是应该知道啊,那如果再误判的话是不是就被用户喷了啊。
但是其实也是可以借助布隆过滤器处理的,而且这种情况反而更能体现布隆“过滤器”的价值。
怎么做呢?
还是把用户的手机号都放到布隆里面
在这里插入图片描述
然后新用户注册的时候,如果这个手机号不在布隆里面,那就可以直接返回,因为我们上面分析了判断不在是一定准确的。
那如果反馈的信息是已存在,那这时候就可能出现了误判,那这时候我们再去数据库里面进行一个确认,然后再返回。
在这里插入图片描述
那这样的话其实就可以认为布隆过滤器实现了一个很好的“过滤”的作用。
它能够把大多数不在的情况快速的“过滤”掉,只剩下少数在的情况需要去数据库里面查询确认,那这样与全部到数据库里面查找还是效率高了很多的可以避免不必要的查询操作,节省时间和资源。

所以对于布隆过滤器来说:

相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。
常见的实践有:利用布隆过滤器减少磁盘 IO 或者网络请求,因为一旦一个值必定不存在的话,我们可以不用进行后续昂贵的查询请求。

6. 如何选择布隆过滤器的长度和哈希函数的个数

那大家思考一下,如果我们现在有N个待插入数据,那布隆过滤器底层的位图我们要开多大呢?哈希函数要选择多少个呢?

就开N个吗?好像不行,因为一个值就要映射多个位置啊。
然后哈希函数多一点的话,误判率肯定会小一点,但是哈希函数也不能搞太多,太多的话一个值映射的位置就会变多,那使用的空间就会变大。

那怎么样选择比较合适呢?有人给出了这样的公式:

k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率
则:
在这里插入图片描述

那按照我们上面写的,我们给了3个哈希函数:

则K为3,然后ln2大概为0.69
那么可以得出
在这里插入图片描述
m=4.3*n,即布隆过滤器的长度等于元素个数的4到5倍是比较合适的,一个元素分配4到5个空间

那我们就按照这个关系去改造一下我们实现的布隆过滤器,因为前面我们都没有考虑这个:

在这里插入图片描述
相关的地方也要改一下
在这里插入图片描述

7. 测试

我们来搞一点数据测试测试

先来看一下set:

在这里插入图片描述
每次set我们可以打印一下它映射的3个位置
在这里插入图片描述
运行一下
在这里插入图片描述
目前我们这些数据是没什么冲突的。

然后test我们也测试一下:

在这里插入图片描述
在这里插入图片描述
🆗,没问题,不过我们的数据量也比较小。

所以呢,我这里也有一个写好的测试的程序,我们来测试几把:

void test_bloomfilter2()
{
	srand((unsigned int)time(nullptr));
	const size_t N = 10000;
	BloomFilter<N> bf;

	std::vector<std::string> v1;
	std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";

	for (size_t i = 0; i < N; ++i)
	{
		v1.push_back(url + std::to_string(i));
	}

	for (auto& str : v1)
	{
		bf.set(str);
	}

	// v2跟v1是相似字符串集,但是不一样
	std::vector<std::string> v2;
	for (size_t i = 0; i < N; ++i)
	{
		std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
		url += std::to_string(999999 + i);
		v2.push_back(url);
	}

	size_t n2 = 0;
	for (auto& str : v2)
	{
		if (bf.test(str))
		{
			++n2;
		}
	}
	cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;

	// 不相似字符串集
	std::vector<std::string> v3;
	for (size_t i = 0; i < N; ++i)
	{
		string url = "https://www.cctalk.com/m/statistics/live/16845432622875";
		url += std::to_string(i + rand());
		v3.push_back(url);
	}

	size_t n3 = 0;
	for (auto& str : v3)
	{
		if (bf.test(str))
		{
			++n3;
		}
	}
	cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
}

简单解释一下,大家应该很容易看懂。这段代码其实就是搞大量的字符串,N可以控制插入字符串的数量。
三个vector v1、v2、v3里面插入的字符串数量都是N,我们搞了一个url字符串,v1里面插入的都是这个url+to_string(i),i在循环过程是不断变化的嘛。
v1set到一个布隆过滤器里面
v2插入的都是这个url+to_string(999999 + i),所以v2跟v1是相似字符串集,但是不一样
然后v3就用了另一个url+to_string(i + rand()),所以 v3跟v1不相似字符串集
然后我们先后遍历v2,v3,判断它们里面的每个字符串在不在布隆过滤器里面,最后得到两个误判率(分别对应字符串相似和不相似的情况下)

我们来运行一下:

首先N等于1万的时候,我们看一下
在这里插入图片描述
大家看,相似不相似都没差多少。但是这个误判率其实还是比较高的,超过了10%。
来10万个试一下(嫌慢可以换成release)
在这里插入图片描述
现在基本在10~20%

那我们如何能控制一下这个误判率呢?

可以适当增加哈希函数的数量,但这个有点治标不治本。
我们可以去扩大空间比较好,之前我们设置的倍数是5,那我们扩大到6试一下
在这里插入图片描述
🆗,还是有一个明显的下降。
来增到7呢?
在这里插入图片描述
会再降低一点,不会再降太多了。
来直接到10
在这里插入图片描述
就降低到1%左右了。
所以去扩大这个空间还是比较有效的降低误判率。

8. 布隆过滤器删除(reset)的思考

大家会发现我们上面没有实现reset,布隆过滤器可以置成reset(删除)吗?

🆗,传统的布隆过滤器是不支持删除操作的。

为什么不支持呢?

因为你删除一个元素之后可能会对其它元素的查找造成影响。
举个栗子
在这里插入图片描述
这里我们如果先把美团删除的话,那就把美团映射的两个位置置成0嘛,然后查找B站的时候,会发现B站映射的两个位置有一个为0,那就会误判B站是不存在的,但是B站我们并没有删除。

那有没有什么办法能让他支持删除呢?有人提出这样一种方法:

就是不再让布隆过滤器的每个位置存储0或1,而是让它直接存储这个位置被set的次数
在这里插入图片描述
这样
在这里插入图片描述
那这样我们把美团删除的话,就把它映射两个位置的次数减一,然后再查找B站就不受影响了。
但这样做的话就涉及到你要给每个位置分配几个位的问题,因为你分配的bit数量不同,那它能存储的次数的范围就不同。
所以这样就会存在一些问题:
就是你的位数如果给的不合适,可能某一次次数更新之后就会溢出,造成计数回绕(计数器值增加到达其最大范围后,再次增加会导致计数器值重新回到初始状态),而且这样做使用的空间肯定会变多
除此之外还存在一个问题:
就是我们查找一个元素的时候无法确认该元素是否真的存于布隆过滤器中。
因为我们删除一个元素的时候一定要确保它是存在的,再去删除(减减对应位置的次数),不存在是不能删除的,但是判断一个元素是否在布隆过滤器中是可能误判的。
所以我们在删除一个元素的时候无法确认它是否存在。

所以我觉得不能认为这种计数的方法可以实现删除,可以说它提供了实现删除的可能。

9. 布隆过滤器的优缺点分析

布隆过滤器的优点

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

布隆过滤器的缺陷

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

10. 源码

bitset.h

#pragma once
#include <vector>

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;
};

BloomFilter.h

#pragma once
#include "bitset.h"
#include <time.h>
#include <string>

struct BKDRHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash += ch;
			hash *= 31;
		}

		return hash;
	}
};

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


struct DJBHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto e : s)
		{
			hash += (hash << 5) + e;
		}
		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 * _mul;
		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);

		//cout << hash1 << " " << hash2 << " " << hash3 << " " << endl << endl;
	}

	bool test(const K& key)
	{
		size_t len = N * _mul;
		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;
	}

private:
	static const size_t _mul = 6;
	bitset<N*_mul> _bs;
};

void test_bloomfilter1()
{
	BloomFilter<100> bs;
	bs.set("sort");
	bs.set("bloom");
	bs.set("hello world hello bit");
	bs.set("test");
	bs.set("etst");
	bs.set("estt");

	cout << bs.test("sort") << endl;
	cout << bs.test("bloom") << endl;
	cout << bs.test("hello world hello bit") << endl;
	cout << bs.test("etst") << endl;
	cout << bs.test("test") << endl;
	cout << bs.test("estt") << endl;

	cout << bs.test("ssort") << endl;
	cout << bs.test("tors") << endl;
	cout << bs.test("ttes") << endl;
}

void test_bloomfilter2()
{
	srand((unsigned int)time(nullptr));
	const size_t N = 10000;
	BloomFilter<N> bf;

	std::vector<std::string> v1;
	std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";

	for (size_t i = 0; i < N; ++i)
	{
		v1.push_back(url + std::to_string(i));
	}

	for (auto& str : v1)
	{
		bf.set(str);
	}

	// v2跟v1是相似字符串集,但是不一样
	std::vector<std::string> v2;
	for (size_t i = 0; i < N; ++i)
	{
		std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
		url += std::to_string(999999 + i);
		v2.push_back(url);
	}

	size_t n2 = 0;
	for (auto& str : v2)
	{
		if (bf.test(str))
		{
			++n2;
		}
	}
	cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;

	// v3跟v1不相似字符串集
	std::vector<std::string> v3;
	for (size_t i = 0; i < N; ++i)
	{
		string url = "https://www.llllll.com/m/statistics/live/16845432622875";
		url += std::to_string(i + rand());
		v3.push_back(url);
	}

	size_t n3 = 0;
	for (auto& str : v3)
	{
		if (bf.test(str))
		{
			++n3;
		}
	}
	cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
}

Test.cpp

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
using namespace std;
#include "BloomFilter.h"

int main()
{
	test_bloomfilter2();
	return 0;
}

在这里插入图片描述

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

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

相关文章

AOI软件之 CAD图纸导入功能

在这里&#xff0c;我不过多的解释AOI&#xff0c;半导体检测行业内的小伙伴自然会懂&#xff1b;我也不会过多解释何为diemap或者wafer-layout。因为我们本文的核心场景仅仅是cad图纸的解析和基本绘图的二次开发。而且我们紧紧是面向行业内的场景需求来说明此功能。 无图我说…

强大的JTAG边界扫描(4):STM32边界扫描应用

文章目录 1. 获取芯片的BSDL文件2. 硬件连接3. 边界扫描测试4. 总结 试想这样一个场景&#xff0c;我们新设计了一款集成了很多芯片的板卡&#xff0c;包括BGA封装的微控制器&#xff0c;如FPGA/MCU&#xff0c;还有LED、按键、串口、传感器、ADC等基本外设。 我们需要测试一下…

时序分解 | MATLAB实现基于EWT经验小波变换的信号分解分量可视化

时序分解 | MATLAB实现基于EWT经验小波变换的信号分解分量可视化 目录 时序分解 | MATLAB实现基于EWT经验小波变换的信号分解分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 EWT经验小波变换 包含频谱相关系数 可直接运行 Matlab代码 1.可自由设置分量个数&…

【uni-app】—2.必备软件安装

一、Node.js 安装流程 二、微信开发者工具 1.选择自己系统对应安装包下载 2. 安装 设置安装路径 三、HBuilderx 1. 根据系统下载安装包 2. 解压安装包&#xff08;自定义解压路径&#xff09; 3. 运行 四、安卓模拟器&#xff08;夜神&#xff09; 1. 下载安装包 2…

eclipse链接MySQL数据库

在MySQL官网下载驱动 MySQLhttps://www.mysql.com/cn/点击下载&#xff1a; 页面滚动到最下方选择社区版&#xff1a; 选择Java版本: 接下来&#xff0c;需要选择操作系统&#xff0c;我们选择平台独立&#xff1a; eclipse 接下来&#xff0c;我们打开eclipse&#xff0c;新建…

【C语言】异或(^)

一.简介 异或&#xff0c;英文为exclusive OR&#xff0c;缩写成xor 异或&#xff08;xor&#xff09;是一个数学运算符。它应用于逻辑运算。异或的数学符号为“⊕”&#xff0c;计算机符号为“xor”。其运算法则为&#xff1a; a⊕b (a ∧ b) ∨ (a ∧b) 如果a、b两个值不…

2023国赛 C题论文 蔬菜类商品自动定价与补货策略

因为一些不可抗力&#xff0c;下面仅展示小部分论文&#xff0c;其余看文末 一、问题重述 在生鲜超市管理领域&#xff0c;涉及一系列复杂问题&#xff0c;包括供应链管理、定价策略以及市场需求分析等方面。以蔬菜类商品为案例&#xff0c;这些商品在生鲜商超中具有较短的保…

升降压芯片

型号&#xff1a; SC8815 升降压电路基于SC8815实现的可调电源 2-36V连续可调&#xff0c;0.3-6A可调限流 原理图参考 可以观察到控制使用i2c就可以 使用参考链接 【小米120W协议转换器】私有协议转换器2.0_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1qu4y1y72F…

无人机通信协议MAVLink简介

Micro Air Vehicle Link(简称MAVLink)用于无人系统(例如,机器人、无人机、无人车、无人船和无人潜航器)。它定义了一组无人系统和地面站之间的消息交换规则。此协议广泛用于无人驾驶系统中,特别是ArduPilot和PX4无人驾驶系统,MAVLink协议提供了强大的功能,不仅用于监视…

二维码智慧门牌管理系统:高效、精准的门牌管理解决方案

文章目录 前言一、问题概述二、解决方案三、实际效果 前言 随着城市发展的日新月异&#xff0c;门牌标识作为城市管理的重要组成部分&#xff0c;也面临着诸多挑战。传统门牌的陈旧、缺失以及指示不明确等问题&#xff0c;已成为城市管理效率和居民生活品质的瓶颈。为解决这些…

【动手学深度学习笔记】--门控循环单元GRU

文章目录 门控循环单元GRU1.门控隐状态1.1重置门和更新门1.2候选隐状态1.3隐状态 2.从零开始实现2.1读取数据2.2初始化模型参数2.3定义模型2.4训练与预测 3.简洁实现 门控循环单元GRU 学习视频&#xff1a;门控循环单元&#xff08;GRU&#xff09;【动手学深度学习v2】 官方…

iOS App上架新规解析:如何进行App备案

摘要 本文将以iOS技术博主的身份&#xff0c;解析iOS App上架新规中的App备案要求。通过探讨备案对开发者和市场的影响&#xff0c;介绍备案流程和所需材料&#xff0c;帮助开发者了解如何进行App备案。 引言 近年来&#xff0c;移动应用市场蓬勃发展&#xff0c;但同时也存…

数据库基础——数据库、数据表和SQL语句

数据库、数据表和SQL语句 数据库、数据表和SQL语句是什么&#xff1f;数据库安装数据库登录及退出创建、查看数据库及修改名字查看及修改数据库编码删除数据库使用或查看当前正在使用的数据库创建、查看数据表及修改名字查看及修改数据表编码查看及修改数据表结构增加约束删除约…

概念解析 | 非极大值抑制(NMS):原理、缺点和改进

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:非极大值抑制(NMS)及其改进工作。 非极大值抑制(NMS):原理、缺点和改进 1. 背景介绍 在计算机视觉中,物体检测是一个核心且充满挑战的问题。众多算法和技术应运而生,其…

离散数学_十章-图 ( 6 ):欧拉通路与哈密顿通路

&#x1f4f7;10.6 欧拉通路与哈密顿通路 1. 欧拉通路与欧拉回路1.1 定义1.2 性质1.3 *寻找欧拉通路(了解) 2. 哈密顿通路和哈密顿回路2.1 定义2.2 性质2.3定理**狄拉克定理****欧尔定理** 1. 欧拉通路与欧拉回路 欧拉通路是一种特殊的图路径&#xff0c;它要求在一个图中通过…

如何防范 AI 盗取你的密码

现如今&#xff0c;随着人工智能&#xff08;AI&#xff09;应用的普及和快速迭代&#xff0c;几乎任何人都可以轻而易举地利用AI进行密码破解之类的攻击。这已经引起了业界的担忧。下面&#xff0c;我将围绕着&#xff1a;密码破解究竟意味着什么&#xff0c;基于AI的密码猜测…

抖店商品卡流量怎么做?给你们说下其中的猜你喜欢,是怎么玩的

我是王路飞。 抖店的商品卡流量玩法&#xff0c;因为是免费的&#xff0c;现在也是平台大力扶持的。 所以很受一些新手商家的喜欢&#xff0c;毕竟是免费的流量&#xff0c;自己的利润也会增加。 我这边的话一直都是找达人带货玩法为主&#xff0c;自然流量为辅&#xff0c;…

怎么扫码听音频?音频在线生码的方法

现在很多小伙伴喜欢听书而不是自己看&#xff0c;那么当我们想分享一段听书音频时&#xff0c;有什么的方法能够更快更好地来让其他人获取内容呢&#xff1f;想要提高传播的效率&#xff0c;那么制作音频二维码&#xff08;音视频二维码制作-一键免费生成音视频二维码在线工具-…

Windows 点击任务栏图标没有反应

事情是这样的 我在 Windows 系统点击任务栏的虚拟机&#xff0c;点击没有反应。 怎么办啊 右键任务栏&#xff0c;选择任务管理器 找到对应的服务&#xff0c;鼠标右键&#xff0c;选择最大化。 就可以在屏幕显示了

听觉刺激期间的神经血管耦合:ERPs和fNIRS血流动力学

导读 强度依赖性振幅变化(IDAP)已在事件相关电位(ERPs)中进行了广泛的研究&#xff0c;并与多种精神疾病相关联。本研究旨在探讨功能近红外光谱(fNIRS)在IDAP范式中的应用&#xff0c;该范式与ERPs相关&#xff0c;可以指示神经血管耦合的存在。两个实验分别有33和31名参与者。…