哈希表(C++实现)

文章目录

  • 写在前面
  • 1. 哈希概念
  • 2. 哈希冲突
  • 3. 哈希函数
  • 4.哈希冲突解决
    • 4.1 闭散列
      • 4.1.1 线性探测
      • 4.1.2 采用线性探测的方式解决哈希冲突实现哈希表
      • 4.1.3 二次探测
    • 4.2 开散列
      • 4.2.2 采用链地址法的方式解决哈希冲突实现哈希表

写在前面

在我们之前实现的所有数据结构中(比如:顺序结构以及平衡树中),要存储的元素与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过多次的比较。如顺序查找时间复杂度为O(N),平衡树中为树的高度,即O( l o g 2 N log_2 N log2N),搜索的效率取决于搜索过程中元素的比较次数。
而我们理想的一种搜索方法为:可以不经过任何比较,一次直接从表中得到要搜索的元素
下面我们来详细介绍一下这种方法:哈希(散列)方法

1. 哈希概念

哈希(Hash):是一种通过特定函数(哈希函数,hash function)将数据映射到固定大小的存储空间的方法。其核心思想是将数据(通常是键值对)通过哈希函数将数据的关键码转换为哈希值,进而通过哈希值快速找到元素对应的存储位置。
ps:哈希函数 hashFunc,将元素的关键码(如字符串、数字等)转换成一个固定范围内的整数,这个整数就是哈希值。
如果利用哈希构造一种存储结构,使得元素的存储位置与它的关键码之间能够建立起一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中:

  • 插入元素
    根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。
  • 搜索元素
    对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。

这种通过哈希方法构造出来的结构称为哈希表(Hash Table)(或者称散列表)。

例如:在哈希表中,存储以下关键字序列{1,7,6,4,5,9};

哈希函数设置为:hash(key) = key % size; size为存储元素的表的长度。

在这里插入图片描述
如果继续向表中插入14时,通过hash函数计算出其位置为:hash(14) = 4;与关键字4计算出的哈希地址相同,发生了冲突,这种现象叫做哈希冲突或哈希碰撞

2. 哈希冲突

哈希冲突:对于两个数据元素的关键字 k i k_i ki k j k_j kj(i != j),有 k i k_i ki != k j k_j kj,但有:Hash( k i k_i ki) == Hash( k j k_j kj),即:不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞
ps:把具有不同关键码而具有相同哈希地址的数据元素称为“同义词。
而引起哈希冲突的一个原因可能是:哈希函数设计不够合理。下面我们来了解一下常见的hash函数。

3. 哈希函数

哈希函数设计原则

  1. 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间。
  2. 哈希函数计算出来的地址能均匀分布在整个空间中。
  3. 哈希函数应该比较简单。

常见哈希函数:

  1. 直接定址法–(常用)
    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B。
    优点:简单、均匀。
    缺点:需要事先知道关键字的分布情况。
    使用场景:适合查找比较小且连续的情况。
  2. 除留余数法–(常用)
    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址。
  3. 平方取中法–(了解)
    假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址;再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址。平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况。
  4. 折叠法–(了解)
    折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的况。
  5. 随机数法–(了解)
    选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为随机数函数。通常应用于关键字长度不等时采用此法。
  6. 数学分析法–(了解)
    设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀的情况。

注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突。

4.哈希冲突解决

解决哈希冲突两种常见的方法是:闭散列和开散列

4.1 闭散列

闭散列,也叫开放定址法(Open Addressing),是一种处理哈希冲突的方法。当发生哈希冲突时,说明hash表没被填满,则表中必然存在空位置,因此我们可以通过探测算法在哈希表中寻找下一个空闲位置来存放元素。开放定址法的核心思想是通过一个探测算法在哈希表中一直探测,直到探测到空位置来解决冲突的。
常见的探测方法包括:线性探测、二次探测等。

4.1.1 线性探测

线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
例如在上面的hash表中继续插入14时,通过hash函数计算出其位置为:hash(14) = 4;与关键字4计算出的哈希地址相同发生了冲突,因此我们使用线性探测找到下一个空位置,插入新元素。
在这里插入图片描述

4.1.2 采用线性探测的方式解决哈希冲突实现哈希表

关于哈希表结构的几点解释:

  • 哈希表存储元素的底层是个顺序表,我们这里直接采用vector来充当顺序表。
  • 底层存储的数据是存储的键值对,类型为 pair<K, V>,其中 K 是键的类型,V 是值的类型。
  • 由于采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如在上面的哈希表中删除元素4,如果直接删除掉,14查找起来可能会受影响。
    在这里插入图片描述因此,哈希表的每个位置不仅要存数据,还要存当前位置的状态,EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除。此时我们在删除掉4的同时将该位置的状态置为DELETE,后面再查找14的时候计算出14的存储位置为4,此时当前位置的状态为DELETE,需要继续往下一个位置查找,这样就可以保证删除元素不会影响其他元素的搜索。

存储结构的定义:

  • 由于表中每个位置既要存储元素,又要存储当前位置的状态,因此我们定义一个结构体 HashData 来存储每个位置的状态和数据。

HashData定义如下:

//所有状态
enum state
{
	EXIST,
	EMPTY,
	DELETE
};
template<class K, class V>
struct HashData
{
	pair<K, V> _kv;//存储的键值对
	state _st;//状态
	//构造
	HashData(const pair<K, V>& kv = pair<K, V>())
		:_kv(kv)
		,_st(EMPTY)
	{}
};

哈希表的定义:

  • 哈希表中不仅要有存储数据的顺序表,还要维护一个成员变量来记录当前表中的元素个数,这是因为它可以帮助计算装载因子,判断是否需要扩容。
  • 装载因子(Load Factor)是哈希表中元素个数与哈希表大小的比值。当装载因子过高时,意味着哈希表中存储的元素数量接近了表的容量,此时在插入元素时可能会导致哈希冲突频繁,从而影响查找效率。为了避免装载因子过高导致的问题,我们因该在装载因子超过某个阈值(如0.7)时,自动扩展哈希表的容量(通常是原容量的两倍),并将现有元素重新分布到新的哈希表中

在这里插入图片描述
哈希表的定义如下:

template<class K, class V, class Hash = HashFuc<K>>
class HastTable
{
public:
	//默认构造
	HastTable()
	{
		//初始时表的长度为10
		_table.resize(10);
		_n = 0;
	}
private:
	vector<HashData<K, V>> _table;
	int _n;//当前表中元素个数
};

哈希表的插入:
步骤如下:

  1. 判断是否需要扩容,这里设定载荷因子超过0.7时要进行扩容操作,将现有元素重新映射到新的哈希表中。
  2. 通过哈希函数获取待插入元素在哈希表中的位置。
  3. 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素;同时将该位置的状态改为EXIST, 表中个数+1。

例如:在哈希表中,依次插入一下关键字序列{1,7,6,4,5,9,11,14};
画个图理解一下:
在这里插入图片描述
具体代码如下:

bool insert(const pair<K, V>& kv)
{
	//key已经存在 无法插入,不允许key相同
	if (find(kv.first)) return false;
	//负载因子超过0.7扩容
	if (_n * 10 / _table.size() == 7)
	{
		//扩容
		HastTable<K, V> newHT;
		newHT._table.resize(_table.size() * 2);
		for (size_t i = 0; i < _table.size(); ++i)
		{
			if (_table[i]._st == EXIST)
			{
				newHT.insert(_table[i]._kv);
			}
		}
		_table.swap(newHT._table);
	}
	Hash h;
	//通过hash函数计算当前插入元素的位置
	size_t hashi = h(kv.first) % _table.size();
	while (_table[hashi]._st == EXIST)
	{
		++hashi;
		hashi %= _table.size();
	}
	_table[hashi]._kv = kv;
	_table[hashi]._st = EXIST;
	++_n;
	return true;
}

哈希表的删除:

上面我们分析了,采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。因此在删除元素的时候采取伪删除,即要删除元素时,先去查找该元素,若找到了将该位置的标志改为DELETE即可。
具体代码如下:

bool erase(const K& k)
{
	//伪删除
	HashData<K, V>* ret = find(k);
	//找到了将该位置的标志改为DELETE,同时表中的元素个数-1
	if (ret)
	{
		ret->_st = DELETE;
		--_n;
		return true;
	}
	return false;
}

哈希表的查找:
步骤如下:

  1. 计算哈希值:使用哈希函数对键 k 进行哈希计算,得到哈希值,并将其映射到哈希表的索引范围内。
  2. 检查当前位置:循环检查哈希表中的索引位置,直到找到目标元素或者确认元素不存在。当前位置为空(状态为 EMPTY):如果当前索引位置为空(状态为 EMPTY),表示该键不在哈希表中,可以直接返回 nullptr。当前位置已存在元素(状态为 EXIST)且键匹配:如果当前索引位置的状态为 EXIST 且键匹配,则找到了目标元素,返回该元素的指针。当前位置已存在元素但键不匹配:如果当前索引位置的状态为 EXIST 但键不匹配,继续检查下一个位置(线性探测)。
  3. 循环检查:继续检查下一个位置,直到找到目标元素或遇到一个空位置(状态为 EMPTY),表示该键不在哈希表中。
  4. 返回结果:如果找到了目标元素,返回该元素的指针;如果找不到,返回 nullptr。

具体代码如下:

HashData<K, V>* find(const K& k)
{
	Hash h;
	//计算哈希值
	size_t hashi = h(k) % _table.size();
	//循环检查:继续检查下一个位置,直到找到目标元素或遇到一个空位置
	while (_table[hashi]._st != EMPTY)
	{
		//如果当前索引位置的状态为 EXIST 且键匹配,则找到了目标元素,返回该元素的指针。
		if (_table[hashi]._st == EXIST && _table[hashi]._kv.first == k)
		{
			return &_table[hashi];
		}
		//如果当前索引位置的状态为 EXIST 但键不匹配,继续检查下一个位置(线性探测)。
		++hashi;
		hashi %= _table.size();
	}
	//遇到空位置(状态为 EMPTY),表示该键不在哈希表中,可以直接返回 nullptr。
	return nullptr;
}

总之,线性探测优点是实现非常简单;线性探测缺点是一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。 比如这组关键字序列:{4,5,14,15,24,6},会造成冲突连成一片,导致效率降低。

4.1.3 二次探测

线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为: H i H_i Hi = ( H 0 H_0 H0 + i 2 i^2 i2 )% m, 或者: H i H_i Hi = ( H 0 H_0 H0 - i 2 i^2 i2 )% m。其中:i = 1,2,3…, H 0 H_0 H0是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小。即每次探测不是往后探测一个位置,而是依次探测 1 2 1^2 12 2 2 2^2 22 3 2 3^2 32,…,这里就不在赘述,有兴趣的可以自己去实现一下。

4.2 开散列

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

例如:在哈希表中,存储以下关键字序列{1,7,6,4,5,9};

哈希函数设置为:hash(key) = key % size; size为存储元素的表的长度。

在这里插入图片描述
当继续插入元素14时,通过哈希函数计算出其存储位置为:hash(14) = 14 % 10 = 4;直接将其插入在位置为4的那个链表中即可。
在这里插入图片描述

4.2.2 采用链地址法的方式解决哈希冲突实现哈希表

存储结构的定义:

  • 由于每个桶都是一个单链表,因此哈希表里面存储的是每个桶的首节点的地址,所以存储底层为指针数组。而每个节点不仅要存储数据还要存储下一个节点的地址,因此我们定义一个结构体 HashNode。

HashNode定义如下:

template<class K, class V>
struct HashNode
{
	typedef HashNode<K, V> Node;
	pair<K, V> _kv;
	Node* _next;
	//构造
	HashNode(const pair<K, V>& kv)
		:_kv(kv)
		, _next(nullptr)
	{}

};

哈希表的定义:

  • 采用链地址法的方式解决哈希冲突实现哈希表也要维护一个成员变量来记录当前表中的元素个数,来帮助计算装载因子,判断是否需要扩容。

哈希表的定义如下:

template<class K, class V, class Hash = HashFuc<K>>
class HastTable
{
	typedef HashNode<K, V> Node;
public:
	//默认构造
	HastTable()
	{
		_table.resize(10);
		_n = 0;
	}
private:
	vector<Node*> _table;
	int _n;//当前表中元素个数
};

哈希表的插入:
步骤如下:

  1. 判断是否需要扩容,这里设定载荷因子超过1时要进行扩容操作,将现有元素重新映射到新的哈希表中。
    ps:关于这里的载荷因子为啥可以到1才进行扩容:桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希表进行增容,那该条件怎么确认呢?开散列最好的情况是:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。
  2. 通过哈希函数获取待插入元素在哈希表中的位置。
  3. 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素;同时将该位置的状态改为EXIST, 表中个数+1。

例如:在哈希表中,依次插入一下关键字序列{1,7,6,4,5,9,11,14, ,2 ,12,17};
画个图理解一下:
在这里插入图片描述
具体代码如下:

bool insert(const pair<K, V>& kv)
{
	Hash h;
	if (find(kv.first)) return false;
	if (_n / _table.size() == 1)
	{
		//扩容
		HastTable<K, V, Hash> ht;
		ht._table.resize(_table.size() * 2);
		//遍历旧哈希表中的所有元素,将每个元素重新映射到新哈希表中。
		for (int i = 0; i < _table.size(); ++i)
		{
			Node* cur = _table[i];
			while (cur)
			{
				Node* next = cur->_next;
				int hashi = h(cur->_kv.first) % ht._table.size();
				cur->_next = ht._table[hashi];
				ht._table[hashi] = cur;
				cur = next;
			}
			_table[i] = nullptr;
		}
		_table.swap(ht._table);
	}
	//扩容完成后,将新元素插入到新的哈希表中。
	Node* newnode = new Node(kv);
	int hashi = h(kv.first) % _table.size();
	newnode->_next = _table[hashi];
	_table[hashi] = newnode;
	++_n;
	return true;
}

ps: 在扩完容后,将现有元素重新映射到新的哈希表时,要依次取旧表中的节点重新进行映射。这是因为重新链接节点通常比创建和插入新节点更高效,因为创建新节点涉及分配新的内存,而重新链接只需要调整指针重新链接的开销较低,可以更快地完成扩容操作

哈希表的删除:
删除步骤如下:

  1. 计算哈希值:使用哈希函数计算要删除元素的哈希值,并取模计算出其在哈希表中的索引。
  2. 查找元素:从计算出的索引开始,遍历链表,查找目标元素。
  3. 删除元素:如果找到目标元素,根据其在链表中的位置,调整链表指针以删除该节点。如果目标元素是链表中的第一个节点,将哈希表该位置指向下一个节点。如果目标元素在链表中间或末尾,将前一个节点的指针指向当前节点的下一个节点。释放被删除节点的内存,减少元素计数。
  4. 返回结果:如果找到并删除了目标元素,返回 true;否则,遍历结束后返回 false,表示未找到目标元素。

具体代码如下:

bool erase(const K& k)
{
	Hash h;
	int hashi = h(k) % _table.size();
	Node* prev = nullptr;
	Node* cur = _table[hashi];
	while (cur)
	{
		if (cur->_kv.first == k)
		{
			//删除
			if (prev == nullptr)
			{
				_table[hashi] = cur->_next;
			}
			else
			{
				prev->_next = cur->_next;
			}
			delete cur;
			--_n;
			return true;
		}
		prev = cur;
		cur = cur->_next;
	}
	return false;
}

哈希表的查找:
步骤如下:

  1. 计算哈希值:使用哈希函数计算键的哈希值,并取模计算出其在哈希表中的索引位置。
  2. 遍历链表: 从计算出的索引位置开始,遍历链表,查找目标元素。
  3. 检查节点:如果当前节点不为空,检查当前节点的键是否等于目标键。
    如果找到目标键,返回 true,表示键存在。如果当前节点的键不等于目标键,继续遍历下一个节点
  4. 返回结果:如果遍历链表结束后未找到目标键,返回 false,表示键不存在。

具体代码如下:

bool find(const K& k)
{
	Hash h;
	int hashi = h(k) % _table.size();
	Node* cur = _table[hashi];
	while (cur)
	{
		if (cur->_kv.first == k)
		{
			return true;
		}
		cur = cur->_next;
	}
	return false;
}

思考: 哈希函数采用处理余数法,被模的key必须要为整形才可以处理,而上面实现的hash表只能存储key为整形的元素,其他类型怎么解决?
要解决哈希表存储非整型键的问题,常见的做法是设计适用于各种键类型的哈希函数。这可以通过定义模板类和泛型哈希函数来实现,允许哈希表存储不同类型的键(如字符串、对象等)。
解决方案

  1. 模板类和泛型哈希函数:使用模板类定义哈希表,使其能够接受不同类型的键。为不同类型的键定义特定的哈希函数。模板特化和偏特化:对于标准类型(如整数、字符串等),可以定义特化的哈希函数。
  2. 对于自定义对象,可以重载哈希函数或者提供自定义哈希函数对象。

可以看到上面实现的hash表的第三个类模板参数可以接收使用者传的自定义哈希函数对象,来将key转换成整型。这个是字符串哈希算法(点击即可跳转)有兴趣的可以自己研究一下。
这里直接给出我的解决方案代码:

template<class K>
struct HashFuc
{
	unsigned long operator()(const K& K)
	{
		return (unsigned long)K;
	}
};
//模板特化 string版本
template<>
struct HashFuc<string>
{
	unsigned long operator()(const string& s)
	{
		unsigned long h = 0, g;

		for (auto e : s)
		{
			h = (h << 4) + e;
			if (g = h & 0xF0000000)
			{
				h ^= g >> 24;

			}
			h &= ~g;
		}
		return h;
	}
};

至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。

创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

如果本篇博客有任何错误,请批评指教,不胜感激 !!!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【详解】RV1106移植opencv-mobile库

文章目录 前言一、烧入镜像二、编译项目1.创建项目文件 三、移植四、运行文件五、总结 前言 硬件&#xff1a;瑞芯微Rv1106【Luckfox Pro\Max Pico、网线一根、USB线、串口助手、摄像头 软件&#xff1a;ubuntu 20.4 编译器&#xff1a;arm-rockchip830-linux-uclibcgnueabihf…

昇思25天学习打卡营第6天|网络构建

网络构建 概念模型模型参数 概念 神经网络模型是由神经网络层和Tensor操作构成的&#xff0c;mindspore.nn提供了常见神经网络层的实现&#xff0c;在MindSpore中&#xff0c;Cell类是构建所有网络的基类&#xff0c;也是网络的基本单元。一个神经网络模型表示为一个Cell&…

入选顶会ICML,清华AIR等联合发布蛋白质语言模型ESM-AA,超越传统SOTA

作为细胞内无数生化反应的驱动力&#xff0c;蛋白质在细胞微观世界中扮演着建筑师和工程师的角色&#xff0c;不仅催化着生命活动&#xff0c;更是构筑、维系生物体形态与功能的基础构件。正是蛋白质之间的互动、协同作用&#xff0c;支撑起了生命的宏伟蓝图。 然而&#xff0…

RK3568驱动指南|第十五篇 I2C-第166章 初步认识I2C

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

无线物联网练习题

文章目录 选择填空简答大题 选择 不属于物联网感知技术的是(A) A:ZigBee B:红外传感器 C:FRID D:传感器 ZigBee是一种无线通信技术&#xff0c;虽然它常用于物联网中作为设备之间的通信手段&#xff0c;但它本身并不是一种感知技术 关于物联网于与互联网的区别的描述&#xff…

在线疫苗预约小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;工作人员管理&#xff0c;管理员管理&#xff0c;用户管理&#xff0c;疫苗管理&#xff0c;论坛管理&#xff0c;公告管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;公告&#xff0c;疫苗…

机器人控制系列教程之并联机器人简介

背景 根据其构件的连接是否构成闭环形式&#xff0c;机器人可分为串联机器人和并联机器人两种。对于串联机器人&#xff0c;其所有的构件以串联的结构形式连接起来&#xff0c;在空间组成一种开环结构&#xff0c;因而具有工作空间大&#xff0c;灵活性好等优点&#xff0c;但…

MySQL之高可用性和应用层优化(一)

高可用性 故障转移和故障恢复 在应用中处理故障转移 有时候让应用来处理故障转移会更加简单或者更加灵活。例如&#xff0c;如果应用遇到一个错误&#xff0c;这个错误外部观察者正常情况下是无法察觉的&#xff0c;例如关于数据库损坏的错误日志信息&#xff0c;那么应用可…

C++算法学习心得八.动态规划算法(6)

1.最长递增子序列&#xff08;300题&#xff09; 题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&…

Kaggle竞赛——房价预测

目录 1. 特征分析1.1 数据集导入1.2 统计缺失值1.3 可视化缺失值1.4 缺失值相关性分析1.5 训练集和测试集缺失数据对比1.6 统计特征的数据类型1.7 数值型特征分布直方图1.8 数值型特征与房价的线性关系1.9 非数值型特征的分布直方图1.10 非数值型特征箱线图1.11 数值型特征填充…

代码随想录算法训练营第55天(py)| 单调栈 | 42. 接雨水*、84.柱状图中最大的矩形

42. 接雨水* 力扣链接 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 思路1 暴力 按列来计算。每一列雨水的高度&#xff0c;取决于&#xff0c;该列 左侧最高的柱子和右侧最高的柱子中&#xff0c;…

WMS、ERP、MES之间的关系

WMS&#xff08;仓库管理系统&#xff09;、ERP&#xff08;企业资源计划&#xff09;、MES&#xff08;制造执行系统&#xff09;是企业管理和运作中常见的三种系统&#xff0c;它们在不同的层面上发挥作用&#xff0c;但之间又有紧密的联系。三者之间的区别如下&#xff1a; …

【哈哈大一上学的全忘了,重开!!】STM32从零入门物联网开发

本笔记资料来源 &#xff1a;STM32物联网入门30步&#xff1d;单片机物联网入门教程 WIFI连接阿里云物联网CubeMXHAL库蓝牙ESP8266杜洋主讲_哔哩哔哩_bilibili IOT&#xff1a;Internet of things 学习目标&#xff1a; 1.掌握洋桃IoT开发板的各功能以及驱动与基本应用 2.掌…

【C++11:右值引用,列表初始化】

统一列表初始化&#xff1a; 构造函数的函数名与函数体之间增加一个列表&#xff0c;用于对成员初始化 在实例化对象时&#xff0c;支持单/多参数的隐式转化&#xff0c;同时也可以省略符号&#xff0c;让代码更简洁 右值的引用 左值&#xff1a; 左值与右值的重要区别就是能…

tkinter显示图片

tkinter显示图片 效果代码解析打开和显示图像 代码 效果 代码解析 打开和显示图像 def open_image():file_path filedialog.askopenfilename(title"选择图片", filetypes(("PNG文件", "*.png"), ("JPEG文件", "*.jpg;*.jpeg&q…

专题五:Spring源码之初始化容器上下文

上一篇我们通过如下一段基础代码作为切入点&#xff0c;最终找到核心的处理是refresh方法&#xff0c;从今天开始正式进入refresh方法的解读。 public class Main {public static void main(String[] args) {ApplicationContext context new ClassPathXmlApplicationContext(…

2.3章节Python中的数值类型

1.整型数值 2.浮点型数值 3.复数   Python中的数值类型清晰且丰富&#xff0c;主要分为以下几种类型&#xff0c;每种类型都有其特定的用途和特性。 一、整型数值 1.定义&#xff1a;整数类型用于表示整数值&#xff0c;如1、-5、100等。 2.特点&#xff1a; Python 3中的…

面试题-Spring家族与SpringIOC

1.spring家族的介绍 Spring简单图&#xff1a; 2.IOC原理 IOC就是原先代码里需要开发者实现对象的创建和关系依赖&#xff0c;反转交给SpringIOC容器管理对象的生命周期和对象之间的依赖关系。 依赖注入的方式&#xff1a; Setter&#xff1a;实现特定属性的public sette…

RedHat9 | podman容器-续集

一、管理容器存储和网络资源 使用容器来运行简单的进程&#xff0c;然后退出。可以配置容连续运行特定服务&#xff0c;如数据库服务。如果持续运行服务&#xff0c;需要向容器添加更多的资源&#xff0c;如持久存储或对其他网络的访问权限。 针对企业容器平台上的大型部署&a…

数据资产安全策略的定制化之道:深入了解各企业独特需求,量身打造个性化的数据资产保护方案,确保数据安全无虞,助力企业稳健发展

目录 一、引言 二、企业数据资产安全现状分析 &#xff08;一&#xff09;数据安全风险多样化 &#xff08;二&#xff09;传统安全措施难以满足需求 &#xff08;三&#xff09;企业数据资产安全意识亟待提高 三、定制化数据资产安全策略的重要性 &#xff08;一&#…