数据结构之哈希

news2024/12/23 18:59:07

哈希

  • 1. 哈希概念
  • 2. 哈希冲突
  • 3. 哈希冲突解决
    • 3.1 哈希表的闭散列
    • 3.2 哈希表的开散列
  • 2. 哈希的应用
    • 2.1 位图
    • 2.2 布隆过滤器

哈希(Hash)是一种将任意长度的二进制明文映射为较短的二进制串的算法。它是一种重要的存储方式,也是一种常见的检索方法。哈希函数通过特定方式(hash函数)处理输入,生成一个值。这个值等同于存放数据的地址,这个地址里面再把输入的数据进行存储。 哈希算法是一种以较短的信息来保证文件唯一性的标志,这种标志与文件的每一个字节都相关,而且难以找到逆向规律。因此,当原文件发生改变时,其标志的位置也会发生改变,此时的对应方式就不再适应,需要将数据重新进行标对应的位置。
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O( l o g 2 N log_2 N log2N),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素

1. 哈希概念

如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素

当向该结构中:

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

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表。散列方法的主要思想是根据结点的关键码值来确定其存储地址:以关键码值K为自变量,通过一定的函数关系h (K) (称为散列函数),计算出对应的函数值来,把这个值解释为结点的存储地址,将结点存入到此存储单元中。 检索时,用同样的方法计算地址,然后到相应的单元里去取要找的结点。 通过散列方法可以对结点进行快速检索。
在这里插入图片描述
用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快。
如上图,如果再插入14,会出现什么问题?会出现哈希冲突。

2. 哈希冲突

哈希冲突是指两个或多个不同的键值被哈希函数映射到了同一个地址中的情况。这种情况下,一个地址对应多个键值对,而查找时只能找到其中一个键值对,因此会导致查找失败。
引起哈希冲突的一个原因可能是:哈希函数设计不够合理。
哈希函数设计原则:1.哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间;2.哈希函数计算出来的地址能均匀分布在整个空间中。
常见哈希函数

  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)作为哈希地址;平方取中法比较适合的情况:不知道关键字的分布,而位数又不是很大的情况。

3. 哈希冲突解决

哈希冲突是哈希表中常见的问题,解决哈希冲突的方法有很多种,两种常见的方法是:闭散列和开散列。

3.1 哈希表的闭散列

闭散列是一种解决哈希冲突的方法,它将所有的关键字都保存在散列表中,而不是像开放地址法那样只保存一部分。在闭散列中,每个桶都是一个链表,当发生哈希冲突时,新的元素会被插入到对应桶的链表中。这种方法可以避免开放地址法中的聚集现象,并且可以在空间充足的情况下实现快速查找。
线性探索
如上图的场景,现在需要插入元素14,先通过哈希函数计算哈希地址,hashAddr为4,因此14理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
在这里插入图片描述

插入:通过哈希函数获取待插入元素在哈希表中的位置
如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。
删除:采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,14查找起来可能会受影
响。因此线性探测采用标记的伪删除法来删除一个元素。
哈希表扩容是指在哈希表中插入新元素时,如果桶的数量不足以容纳新元素,就需要增加桶的数量。哈希表扩容的过程包括以下几个步骤:
1.创建一个新的桶数组,大小为原来的两倍;2.将原来的桶数组中的元素重新哈希到新的桶数组中;3.释放原来的桶数组。
在哈希表扩容的过程中,需要重新计算每个元素在新桶数组中的位置,这个过程需要消耗一定的时间。因此,在设计哈希表时,需要根据实际情况选择合适的桶数量,以避免频繁扩容。
哈希表的负载因子是指哈希表中已经存储的元素个数与容量的数量之比。负载因子越大,哈希冲突的概率就越大,查找、插入和删除操作的效率也会降低。一般来说,当负载因子超过某个阈值时,就需要对哈希表进行扩容,以保证哈希表的性能。

代码如下:

namespace OpenAddress
{
	enum State
	{
		EMPTY,
		EXIST,
		DELETE
	};

	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY;
	};

	template<class K, class V>
	class HashTable
	{
	public:
		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;

			// 负载因子超过0.7就扩容
			//if ((double)_n / (double)_tables.size() >= 0.7)
			if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
			{
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				HashTable<K, V> newht;
				newht._tables.resize(newsize);

				// 遍历旧表,重新映射到新表
				for (auto& data : _tables)
				{
					if (data._state == EXIST)
					{
						newht.Insert(data._kv);
					}
				}

				_tables.swap(newht._tables);
			}

			size_t hashi = kv.first % _tables.size();

			// 线性探测
			size_t i = 1;
			size_t index = hashi;
			while (_tables[index]._state == EXIST)
			{
				index = hashi + i;
				index %= _tables.size();
				++i;
			}

			_tables[index]._kv = kv;
			_tables[index]._state = EXIST;
			_n++;

			return true;
		}

		HashData<K, V>* Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return false;
			}

			size_t hashi = key % _tables.size();

			// 线性探测
			size_t i = 1;
			size_t index = hashi;
			while (_tables[index]._state != EMPTY)
			{
				if (_tables[index]._state == EXIST && _tables[index]._kv.first == key)
				{
					return &_tables[index];
				}

				index = hashi + i;
				index %= _tables.size();
				++i;
				// 如果已经查找一圈,那么说明全是存在+删除
				if (index == hashi)
					break;
			}

			return nullptr;
		}

		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				ret->_state = DELETE;
				--_n;
				return true;
			}
			else
			{
				return false;
			}
		}

	private:
		vector<HashData<K, V>> _tables;
		size_t _n = 0; // 存储的数据个数
	};
}

线性探测优点:实现非常简单,
线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。如何缓解呢?可以使用二次探索,三次探索等等。当所求出key的位置被占用,不去填入key+1的位置,而是填入key+2或key+3的位置,这种方式叫做二次探索,三次探索,这样可以让表更加稀松一些,就能提高效率。
当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。

3.2 哈希表的开散列

开散列(Open Hashing)是另一种解决哈希冲突的方法,也称为链地址法(Chaining)。在开散列中,哈希表中的每个桶都是一个链表,当发生哈希冲突时,新的元素会被插入到对应桶的链表中。这种方法可以避免开放地址法中的聚集现象,并且可以在空间充足的情况下实现快速查找 。
在这里插入图片描述
开散列增容:桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可
能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希表进行增容,开散列最好的情况是:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。
只能存储key为整形的元素,其他类型怎么解决?key为字符串类型,需要将其转化为整形。在将字符串类型转换为整数类型时,可以使用以下方法:

  1. 将字符串中的每个字符转换为其ASCII码值,然后将这些值相加得到一个整数。
  2. 将字符串中的每个字符转换为其ASCII码值,然后将这些值相乘得到一个整数。
  3. 将字符串中的每个字符转换为其ASCII码值,然后将这些值按位异或得到一个整数。

代码如下:

namespace HashBucket
{
	template<class K, class V>
	struct HashNode
	{
		HashNode<K, V>* _next;
		pair<K, V> _kv;

		HashNode(const pair<K, V>& kv)
			:_next(nullptr)
			, _kv(kv)
		{}
	};
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return key;
		}
	};
	// 特化,将字符串的情况进行一些处理
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& s)
		{
			size_t hash = 0;
			for (auto ch : s)
			{
				hash += ch;
				hash *= 31;
			}

			return hash;
		}
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		~HashTable()
		{
			for (auto& cur : _tables)
			{
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}

				cur = nullptr;
			}
		}
		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
				return nullptr;

			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}

				cur = cur->_next;
			}

			return nullptr;
		}
		bool Erase(const K& key)
		{
			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;

					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}

			return false;
		}
		//扩容扩质数的2倍左右
		size_t GetNextPrime(size_t prime)
		{
			static const int __stl_num_primes = 28;
			static const unsigned long __stl_prime_list[__stl_num_primes] =
			{
				53, 97, 193, 389, 769,
				1543, 3079, 6151, 12289, 24593,
				49157, 98317, 196613, 393241, 786433,
				1572869, 3145739, 6291469, 12582917, 25165843,
				50331653, 100663319, 201326611, 402653189, 805306457,
				1610612741, 3221225473, 4294967291
			};

			size_t i = 0;
			for (; i < __stl_num_primes; ++i)
			{
				if (__stl_prime_list[i] > prime)
					return __stl_prime_list[i];
			}

			return __stl_prime_list[i];
		}
		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
			{
				return false;
			}

			Hash hash;

			// 负载因因子==1时扩容
			if (_n == _tables.size())
			{
				size_t newsize = GetNextPrime(_tables.size());
				vector<Node*> newtables(newsize, nullptr);
				for (auto& cur : _tables)
				{
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hash(cur->_kv.first) % newtables.size();
						// 头插到新表
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;

						cur = next;
					}
				}
				_tables.swap(newtables);
			}
			size_t hashi = hash(kv.first) % _tables.size();
			// 头插
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;

			++_n;
			return true;
		}
		size_t MaxBucketSize()
		{
			size_t max = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				auto cur = _tables[i];
				size_t size = 0;
				while (cur)
				{
					++size;
					cur = cur->_next;
				}
				if (size > max)
				{
					max = size;
				}
			}

			return max;
		}
	private:
		vector<Node*> _tables; // 指针数组
		size_t _n = 0; // 存储有效数据个数
	};
}

2. 哈希的应用

2.1 位图

位图(Bitmap)是一种数据结构,用于表示一个二进制向量。位图中的每个元素都只有两个可能的取值:0和1。位图可以用于压缩数据,减少存储空间的使用,也可以用于快速查找和访问元素。
在位图中,每个元素都只占用一个二进制位,因此可以使用一个整数来表示多个元素。例如,一个32位的整数可以表示32个元素。这种方法可以大大减少存储空间的使用,并且可以在常数时间内访问和修改元素。
位图常用于处理大量数据的问题,例如在搜索引擎中用于记录网页的索引信息。它还可以用于计算机网络中的路由表、缓存和防火墙等。
例如求解给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。
数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。
位图代码如下:

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

2.2 布隆过滤器

当查找大量与字符串有关的数据时,过滤掉那些已经存在的记录。 如何快速查找呢?

  1. 用哈希表存储用户记录,缺点:浪费空间
  2. 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了。
  3. 将哈希与位图结合,即布隆过滤器

布隆过滤器(Bloom Filter)是一种空间效率高、误判率低的概率型数据结构,用于判断一个元素是否在一个集合中。它由一个位数组和多个哈希函数组成。位数组的长度为m,哈希函数的个数为k。当一个元素被加入集合时,它会被k个哈希函数映射成位数组中的k个位置,将这些位置设为1。当判断一个元素是否在集合中时,将这个元素进行k次哈希,得到k个位置。 如果这k个位置都是1,则说明这个元素在集合中;如果这k个位置中有任意一个位置是0,则说明这个元素不在集合中。
布隆过滤器的优点是空间效率高、查询时间短,缺点是有一定的误判率和删除困难。它常用于大规模数据处理中,例如网络爬虫、垃圾邮件过滤等。
布隆过滤器的查找
布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中。
注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。
比如:在布隆过滤器中查找"alibaba"时,假设3个哈希函数计算的哈希值为:1、3、7,刚好和其他元素的比特位重叠,此时布隆过滤器告诉该元素存在,但实该元素是不存在的。
布隆过滤器删除
布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。
比如:删除上图中"tencent"元素,如果直接将该元素所对应的二进制比特位置0,“baidu”元素也被删除了,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠。
一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。

布隆过滤器优点

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

布隆过滤器缺陷

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

// N最多会插入key数据的个数
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 hash1 = Hash1()(key) % N;
		_bs.set(hash1);

		size_t hash2 = Hash2()(key) % N;
		_bs.set(hash2);

		size_t hash3 = Hash3()(key) % N;
		_bs.set(hash3);
	}

	bool test(const K& key)
	{
		size_t hash1 = Hash1()(key) % N;
		if (!_bs.test(hash1))
		{
			return false;
		}

		size_t hash2 = Hash2()(key) % N;
		if (!_bs.test(hash2))
		{
			return false;
		}
		size_t hash3 = Hash3()(key) % N;
		if (!_bs.test(hash3))
		{
			return false;
		}
		return true;
	}
private:
	bitset<N> _bs;
};

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

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

相关文章

Unity——拖尾特效

拖尾是一种很酷的特效。拖尾的原理来自人类的视觉残留&#xff1a;观察快速移动的明亮物体&#xff0c;会看到物体移动的轨迹。摄像机通过调整快门时间&#xff0c;也可以拍出具有拖尾效果的照片&#xff0c;如在城市的夜景中&#xff0c;汽车的尾灯拖曳出红色的线条。 在较老…

平安健康,找到了医疗服务的价值密码

健康是人类的永恒需求&#xff0c;围绕医疗和健康服务衍生的产业&#xff0c;却苦于无法和用户建立足够紧密、长期的联系。由此&#xff0c;也不得不面临价值从何而来的问题。 作为医疗服务领域的代表性企业&#xff0c;平安健康医疗科技有限公司&#xff08;股票简称“平安好…

Oracle解锁表、包、用户、杀会话、停job

Oracle解锁表、包、用户、杀会话、停job 一、创建包tzq_server_pkg二、授权给需要使用的用户log三、解锁表&#xff1a;执行存过unlock_table(schema_name, table_name)四、解锁包&#xff1a;执行存过unlock_package(schema_name, pkg_name)五、解锁用户&#xff1a;执行存过u…

14-数据结构-二叉树的创建以及前中后遍历,以及结点和叶子节点的计算(C语言)

概述&#xff1a; 二叉树&#xff0c;这里采用孩子链表存储法&#xff0c;即一个数据域和两个左右孩子指针域。随后递归进行遍历即可。在创建二叉树的时候&#xff0c;先创建各个二叉树结点&#xff08;这里的结点采用动态分配&#xff0c;因此结点为指针变量&#xff09;&…

c语言中编译过程与预处理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、c语言的编译与链接1、编译与链接概述2、编译与链接详解 二、c语言预处理1.c语言中内置的预定义符号2、#define定义标识符3、#define定义宏4、#define 替换规…

项目进度管理软件:选择最适合您的工具

项目进度管理作为项目管理的重要组成部分&#xff0c;可以帮助项目团队更好地控制项目进度&#xff0c;确保项目按时完成并达到预期目标。本文将介绍项目进度管理软件的功能以及市场上常见的几种项目进度管理软件。 “项目进度管理软件有哪些?功能出色的有Zoho Projects、Trel…

ResNet 模型原理

ResNet与Vgg的主要区别&#xff1a; 1.ResNet相较于Vgg具有更加深的网络结构 2.ResNet相较于Vgg引入了残差连接的结构 3.ResNet引入了BatchNorm层&#xff0c;使得ResNet能够训练更加深的网络结构 4.ResNet使用stride2的卷积层代替了Vgg中池化层进行下采样 5.ResNet相较于…

【在Windows下搭建Tomcat HTTP服务】

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个轻量级的服务器&#xff0c;不仅名字很有趣&#xff0…

报错:1 字节的 UTF-8 序列的字节 1 无效。

这里我的问题出现在BookMapper.xml中 java.lang.IllegalStateException: Failed to load ApplicationContextat org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125)at org.spring…

情人节特别篇:用c++弹奏音乐“海阔天空”与“孤勇者”

W...Y的主页 &#x1f495; 代码库分享 &#x1f60a; 目录 孤勇者 海阔天空 今天是2023年8月22日七夕情人节&#xff0c;但是对我来说就是再普通不过的日子。我相信有很多人期待这一天的到来&#xff0c;和自己的对象出去享受快乐时光。但是我只有一个人独孤的度过短暂的时…

1.7 【MySQL】常用存储引擎

MySQL 支持非常多种存储引擎&#xff0c;我这先列举一些&#xff1a; 存储引擎 描述 ARCHIVE 用于数据存档&#xff08;行被插入后不能再修改&#xff09; BLACKHOLE 丢弃写操作&#xff0c;读操作会返回空内容 CSV 在存储数据时&#xff0c;以逗号分隔各个数据项 FEDE…

自动控制原理笔记-采样控制系统

目录 采样控制系统的基本概念&#xff1a; 采样过程及采样定理&#xff1a; 一、采样过程 二、采样定理&#xff08;香农采样定理、奈奎斯特采样定律&#xff09; 三、信号复现 四、零阶保持器 z变换与z反变换&#xff1a; z变换的定义 z变换基本定理 z反变换 采样系…

Oracle 如何给大表添加带有默认值的字段

一、讲故事 你是否遇到过开发人员添加字段&#xff0c;导致数据库锁表问题&#xff1f; 但是令开发疑惑的事&#xff0c;他们添加字段&#xff0c;有的时候很快&#xff0c;有的时候很慢&#xff1f; 为什么呢&#xff1f; 询问得知&#xff0c;**加的慢时候是带上了default默…

提高软件缺陷探测率的5个重点

缺陷对软件项目的影响不言而喻&#xff0c;如果不重视缺陷的探测率和移除率&#xff0c;往往会对软件产品产生不可估量的破坏性影响&#xff0c;直接影响项目进度甚至项目交付。 因此我们需要高效提高软件缺陷探测率&#xff0c;一般来说有以下5个方面需要重点关注&#xff1a;…

Linux--线程地址空间

1.程序地址空间 先来就看这张图 这是一张程序地址分布的图&#xff0c;通过一段代码来证明地址空间的分布情况 编译结果&#xff1a; 可以看出的是&#xff0c;父子进程中对于同一个变量打印的地址是一样的&#xff0c;这是因为子进程以父进程为模板&#xff0c;因为都没有对数…

深度丨Serverless + AIGC,一场围绕加速创新的升维布局

作者&#xff1a;褚杏娟 上图来源于基于函数计算部署 SD实现光影效果 前言&#xff1a; Serverless 在中国发展这些年&#xff0c;经历了高潮、低谷、现在重新回到大众视野。很多企业都非常感兴趣&#xff0c;部分企业开始大规模应用&#xff1b;也有一些企业对在生产环境真正…

VGG的结构:视觉几何组(Visual Geometry Group)

目录 1. VGG 的结构 2. VGG 的网络细节 3. VGG 的代码实现 1. VGG 的结构 牛津大学的视觉几何组&#xff08;Visual Geometry Group&#xff09;设计了 VGGNet(也称为 VGG)&#xff0c;一种经典的卷积神经网络 (CNN) 架构。在 2014 年 ILSVRC 分类任务中&#xff0c;VGG 取…

回归分析扫盲:为什么非线性模型不能直接用最优子集选择法

最近有人给我发了篇文章&#xff1a; 一个问题有一堆变量&#xff0c;我们要选取哪些变量来建模呢&#xff1f;我们来看看这篇文章是怎么做的&#xff1a; 这个方法简单来说就是&#xff1a;对于这一堆变量&#xff0c;我们每次尝试剔除其中一个变量&#xff0c;然后用剩下的变…

字节流概述,及字节流写数据的三种方式

1.IO流概述和分类 如果数据通过记事本打开&#xff0c;我们还可以读懂里面的内容就使用字符流&#xff0c;否则使用字节流。如果不知道使用哪种类型的流&#xff0c;就使用字节流。 2.字节流写数据 创建字节输出流的时候&#xff0c;一共做了三件事情。 调用系统功能创建了文…

Goland 配置go modules 环境变量

我的配置&#xff0c;仅供参考&#xff1a; GOPROXYhttps://goproxy.cn,direct;GO111MODULEon;GOSUMDBoff;GONOSUMDB*