【C++】:哈希和哈希桶

news2024/11/27 1:32:04

朋友们、伙计们,我们又见面了,本期来给大家解读一下有关哈希和哈希桶的知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

目录

1. 哈希概念

2. 哈希表的插入 

2.1 哈希冲突

2.2 哈希冲突的解决

2.2.1 闭散列(开放定址法)

2.2.2 开散列 (哈希桶/拉链法)

2.3 闭散列的代码实现

2.3.1 闭散列代码优化

2.3.2 闭散列完整代码 

2.4 开散列的代码实现 

2.4.1 开散列代码优化

2.4.2 开散列完整代码 


1. 哈希概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(\log N),搜索的效率取决于搜索过程中元素的比较次数。


理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一 一映射的关系,那么在查找时通过该函数可以很快找到该元素。

2. 哈希表的插入 

1. 直接定址法

根据所要插入的关键值在哈希表中所对应的存储位置建立起的一种一 一映射关系。
这种方法所适合的场景:数据范围集中,数据量较小。

与存储位置的关系是一对一的关系,不存在哈希冲突。

2. 除留余数法

用所要插入的元素和哈希表的大小进行取模操作,这样就可以将大范围的数据缩小,根据对应关系存储在哈希表中。

这种方法所适合的场景:数据范围不集中,且数据量较多。

与存储位置的关系是多对一的关系,并且存在哈希冲突。

2.1 哈希冲突

我们经常使用除留余数法,但是会面临哈希冲突,那么什么是哈希冲突呢?

不同的关键值通过映射关系所得到的存储位置是一样的,这种情况被叫做哈希冲突或者哈希碰撞。

2.2 哈希冲突的解决

2.2.1 闭散列(开放定址法)

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。

那如何寻找下一个空位置呢?


1. 线性探测

从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

hashi + i (i >= 0)

2. 二次探测

从发生冲突的位置开始,依次向后探测,探测的距离是i的平方,直到寻找到下一个空位置为止。

hashi + i^2 (i >= 0) 

2.2.2 开散列 (哈希桶/拉链法)

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

从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。

2.3 闭散列的代码实现

1.  基本构造

为了区分哈希表中的每一个位置值的情况,所以可以设置一个枚举状态值,这个节点的状态值包含:空、存在、删除。

我们采用Key_Value结构来实现哈希表

namespace open_address
{
	//哈希节点状态
	enum Status
	{
		EMPTY,   //空
		EXIST,   //存在
		DELETE   //删除
	};

	//哈希节点
	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		Status _s;         //哈希节点的状态
	};

	//哈希表
	template<class K, class V>
	class HashTable
	{
	public:
		//构造
		HashTable()
		{
			_tables.resize(10);    //初始化先给10个空间
		}
	private:
		vector<HashData<K, V>> _tables;    //
		size_t _n = 0;                 //存储关键字的个数
	};
}

2. 查找 

通过传递关键之key进行查找,返回该关键值的指针,查找的逻辑首先得用key与存储位置的映射关系进行查找,如果查找到了空还没有找到对应的关键值,那么就返回空,映射关系我们采用的是除留余数法。

那么为什么找到空就结束了呢?首先我们根据除留余数法找到key存储的位置,那么这个位置有可能是被哈希冲突所占用的位置,那么本该存储在这个位置的值就需要继续向后面找空位置填充上去,那么key这个值的前面的位置的状态都是存在,所以可以一直向后面找,直到为空就停止。

	//查找
		HashData<K, V>* Find(const K& key)
		{
			size_t hashi = key % _tables.size();  //找对应位置
			//不为空继续查找
			while (_tables[hashi]._s != EMPTY)
			{
				if (_tables[hashi]._s == EXIST
					&&_tables[hashi]._kv.first == key)     //_kv.first存在且等于key表示找到了
				{
					return &_tables[hashi];
				}
				++hashi;
				hashi %= _tables.size();
			}
			return NULL;
		}

3. 插入

插入首先需要查找要插入的key有没有在表中存在,若不存在既可以插入,若存在则不能插入。

插入我们采用除留余数法,用key与表的大小取模得到的映射关系,如果映射到的的位置的状态是存在,那么就需要继续向后面找空余的位置,如果映射到的位置的状态是删除或者空,那么就可以插入,如果映射到的位置后面的空间已满,那么就与表的大小取模,从最前面开始找空余位置。

//插入
		bool Insert(const pair<K, V>& kv)
		{
			//先查找
			if (Find(kv.first))
				return false;
						
            //线性探测
			size_t hashi = kv.first % _tables.size();  //找到对应的映射
			while (_tables[hashi]._s == EXIST)
			{
				hashi++;                     //找到后面空着的位置
				hashi %= _tables.size();     //形成一个圈
			}  

			_tables[hashi]._kv = kv;            //插入
			_tables[hashi]._s = EXIST;      //修改状态
			++_n;                           //关键字个数++
			return true;
		}

写到这里,那么存在一个问题,如果空间满了就需要扩容,那么哪种方式的扩容最容易呢?

可以使用在模拟实现string时,string的拷贝构造的现代写法,那么这种现代写法怎么运用到哈希表中呢?

首先要设置一个负载因子,表示空间使用率,这个负载因子我们设置为百分之70,如果使用率超过了百分之70,我们就需要进行扩容,扩容我们采用2倍扩容的方式,首先将创建一个新的哈希表,大小为原来表的2倍,然后依次遍历旧表,将旧表中的数据依次插入到新表,这时,新的哈希表就是我们想要的,然后将它与旧表交换即可,然后将新表删除即可。

//插入
		bool Insert(const pair<K, V>& kv)
		{
			//先查找
			if (Find(kv.first))
				return false;
			//负载因子设置为0.7
			if (_n * 10 / _tables.size() == 7)
			{
				//扩容
				size_t NewSize = _tables.size() * 2;   //2倍扩容
				HashTable<K, V> NewHash;               //创建新的哈希表
				NewHash._tables.resize(NewSize);       //设置为原来2倍大小

				//遍历旧表将旧表中的内容插入新表
				for (int i = 0; i < _tables.size(); ++i)
				{
					if (_tables[i] == EXIST)
					{
						NewHash.Insert(_tables[i]._kv);
					}
				}
				//交换
				_tables.swap(NewHash._tables);
			}

			//线性探测
			size_t hashi = kv.first % _tables.size();  //找到对应的映射
			while (_tables[hashi]._s == EXIST)
			{
				hashi++;                     //找到后面空着的位置
				hashi %= _tables.size();     //形成一个圈
			}  

			_tables[hashi]._kv = kv;            //插入
			_tables[hashi]._s = EXIST;      //修改状态
			++_n;                           //关键字个数++
			return true;
		}

新表是一个局部变量,出了作用域自动调用它的析构函数,析构函数内置类型不做处理,自定义类型vector会去自动调用vector的析构函数。

4. 删除

这里采用的是伪删除的方法,首先找到需要删除的key,然后将其对应的状态改为删除即可,然后将关键值个数减一。

//伪删除法
		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				ret->_s = DELETE;          //修改状态即可
				_n--;                      //个数--
				return true;            
			}
			else
				return false;
		}

2.3.1 闭散列代码优化

上述代码能看到很明显的局限性,我们在取模的时候给的是整型,但是遇到浮点类型、string类型就不能进行取模操作了,因此需要优化一下,利用仿函数,对与浮点数进行类型强转,然后特化出一份专门处理string类型的,可以将string类型的字符串所有字符的ASCII码加起来进行映射操作。

//仿函数
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

	//特化处理string类型
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& s)
		{
			size_t ret = 0;
			for (auto e : s)
			{
				ret *= 31;  //防止字符一样顺序不一样
				ret += e;   //字符ASCII加起来
			}
			return ret;
		}
	};

2.3.2 闭散列完整代码 

#pragma once
#include <vector>

//开放定址法
namespace open_address
{
	//仿函数
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

	//特化处理string类型
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& s)
		{
			size_t ret = 0;
			for (auto e : s)
			{
				ret *= 31;  //防止字符一样顺序不一样
				ret += e;   //字符ASCII加起来
			}
			return ret;
		}
	};

	//哈希节点状态
	enum Status
	{
		EMPTY,   //空
		EXIST,   //存在
		DELETE   //删除
	};

	//哈希节点
	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		Status _s;         //哈希节点的状态
	};

	//哈希表
	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		//构造
		HashTable()
		{
			_tables.resize(10);    //初始化先给10个空间
		}

		//插入
		bool Insert(const pair<K, V>& kv)
		{
			//先查找
			if (Find(kv.first))
				return false;
			//负载因子设置为0.7
			if (_n * 10 / _tables.size() == 7)
			{
				//扩容
				size_t NewSize = _tables.size() * 2;   //2倍扩容
				HashTable<K, V> NewHash;               //创建新的哈希表
				NewHash._tables.resize(NewSize);       //设置为原来2倍大小

				//遍历旧表将旧表中的内容插入新表
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					if (_tables[i]._s == EXIST)
					{
						NewHash.Insert(_tables[i]._kv);
					}
				}
				//交换
				_tables.swap(NewHash._tables);
			}

			//仿函数
			Hash hs;
			//线性探测
			size_t hashi = hs(kv.first) % _tables.size();  //找到对应的映射
			while (_tables[hashi]._s == EXIST)
			{
				hashi++;                     //找到后面空着的位置
				hashi %= _tables.size();     //形成一个圈
			}  

			_tables[hashi]._kv = kv;            //插入
			_tables[hashi]._s = EXIST;      //修改状态
			++_n;                           //关键字个数++
			return true;
		}

		//查找
		HashData<K, V>* Find(const K& key)
		{
			Hash hs;
			size_t hashi = hs(key) % _tables.size();  //找对应位置
			//不为空继续查找
			while (_tables[hashi]._s != EMPTY)
			{
				if (_tables[hashi]._s == EXIST
					&&_tables[hashi]._kv.first == key)     //_kv.first存在且等于key表示找到了
				{
					return &_tables[hashi];
				}
				++hashi;
				hashi %= _tables.size();
			}
			return NULL;
		}

		//伪删除法
		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				ret->_s = DELETE;          //修改状态即可
				_n--;                      //个数--
				return true;            
			}
			else
				return false;
		}
	private:
		vector<HashData<K, V>> _tables;    //
		size_t _n = 0;                 //存储关键字的个数
	};
}

2.4 开散列的代码实现 

1. 基本构造

开散列又叫做哈希桶,这些桶也就是单链表所链接的节点,将存在哈希冲突的都放在同一个桶中,那么也避免不了冲突过多导致一个桶过长,因此,在部分编程语言中规定了如果一个桶的长度超过了8,那么就将原来挂的单链表转挂成红黑树。由于存在哈希思想,所以哈希桶的平均时间复杂度是O(1)。

同样的我们采用Key_Value的结构来定义哈希节点和哈希表,那么关于哈希表的实现我们可以采用两种方法:

① 使用库中的forward_list来现用,并且还可以根据桶的长度加上红黑树(吃现成)

namespace hash_bucket
{
	template<class K, class T>
	class HashTable
	{
	private:
		struct bucket
		{
			forwad_list<pair<K, V>> _lt;   //单链表
			set<pair<K, V>> _rbtree;       //红黑树
			size_t len = 0; // 超过8,放到红黑树
		};
		vector<bucket> _tables;
		int _n;
	};
}

② 我们自己实现桶的节点(方便封装迭代器)

namespace hash_bucket
{
	template<class K, class T>
	class HashNode
	{
        HashNode(const pair<K, T>& kv)
			:_kv(kv)
			,_next(nullptr)
		    {}
		pair<K, T> _kv;
		HashNode* _next;
	};

	template<class K, class T>
	class HashTable
	{
    public:
		HashTable()
		{
			_tables.resize(10);
		}
	public:
		typedef HashNode<K, T> Node;

	private:
		vector<Node*> _tables;
		size_t _n;
	};
}

我们采用第二种方法实现,便于后面封装unordered_set和unordered_map。

2. 查找

首先用key取模哈希表的大小,从而找到hashi确定在哪个桶,然后再遍历该桶找key

//查找
		Node* Find(const K& key)
		{
            //确定在哪一个桶中
			size_t hashi = key % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
					return cur;
				else cur = cur->_next;
			}
			return nulptr;
		}

3. 插入

哈希桶的插入需要借助于Find,如果这个桶里面存在相同的数据,则不能插入(类似于set机制),如果没有方可插入,哈希桶的负载因子我们一般设置为1,那么哈希桶的插入有两种:尾插和头插,两种方式均可以插入,因为尾插需要找尾,所以我们一般选择头插

那么该怎么对哈希桶进行扩容呢?

可以参考在实现闭散列时的方法:先创建一个容量为旧表容量二倍的新表,采用的遍历旧表将哈希桶中的节点头插到新表的映射位置,然后将旧表与新表实现交换,这样就拿到了新表的数据,也就完成了扩容的操作。

注意:虽然将旧表的桶挪到新表中,但是旧表中的节点还是指向该桶,所以每挪完一个桶需要将旧表的节点置为nullptr。

//插入
bool Insert(const pair<K, T>& kv)
{
	if (Find(kv.first))
		return false;
	//扩容
	//负载因子为1再扩容
	if (_n == _tables.size())
	{
		//创建新的哈希表
		vector<Node*> newTables;
		newTables.resize(_tables.size() * 2, nullptr);
		//遍历旧表
		for (int i = 0; i < _tables.size(); i++)
		{
			//将旧表的节点依次遍历插入到新表
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;      //记录下一个节点
				size_t hashi = cur->kv.first % newTables.size();  //找到在新表里面的映射
				cur->_next = newTables[hashi];  //链接
				newTables[hashi] = cur;
				cur = next;   //找下一个
			}
			_tables[i] = nullptr;  //断开链接
		}
		_tables.swap(newTables);   //拿到新表的数据
	}

	//头插
	size_t hashi = kv.first % _tables.size();
	Node* newnode = new Node(kv);
	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	_n++;
	return true;
}

4. 删除

先通过key的映射查看在哪个桶中,然后遍历该桶,将需要删除的节点的前一个节点prev的next指向要删除节点的下一个节点,然后delete该节点即可完成删除,这个过程需要注意如果prev为空,那么直接将_tables[hashi]指向要删除节点的下一个节点即可。

//删除
bool Erease(const K& key)
{
	size_t hashi = key % _tables.size();
	Node* cur = _tables[hashi];
	Node* prev = nullptr;
	while (cur)
	{
		if (cur->_kv.first == key)
		{
			if (prev == nullptr)
			{
				_tables[hashi] = cur->_next;
			}
			else
			{
				prev->_next = cur->_next;
				delete cur;
				return true;
			}
		}
		prev = cur;
		cur = cur->_next;
	}
	return false;
}

5. 析构函数

由于我们自主实现了一个单链表,所以在析构时需要我们手动的进行释放哈希桶中的一系列节点。

~HashTable()
{
	//手动释放哈希桶
	//遍历哈希表依次释放桶中的节点
	for (size_t i = 0; i < _tables.size(); i++)
	{
		Node* cur = _tables[i];
		while (cur)
		{
			Node* next = cur->_next;
			delete cur;
			cur = next;
		}
		_tables[i] = nullptr;
	}
}

2.4.1 开散列代码优化

根据闭散列的经验,我们还需要给开散列也同样的设置转化整数的仿函数,防止string类型不能转化为整数的缺陷,那么同样的需要用到模板特化,在浮点数转化整型的基础上特化一份转化string类型的仿函数。

//仿函数
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

//特化处理string类型
template<>
struct HashFunc<string>
{
	size_t operator()(const string& s)
	{
		size_t ret = 0;
		for (auto e : s)
		{
			ret *= 31;  //防止字符一样顺序不一样
			ret += e;   //字符ASCII加起来
		}
		return ret;
	}
};

2.4.2 开散列完整代码 

    // 开散列
namespace hash_bucket
{
	//仿函数
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

	//特化处理string类型
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& s)
		{
			size_t ret = 0;
			for (auto e : s)
			{
				ret *= 31;  //防止字符一样顺序不一样
				ret += e;   //字符ASCII加起来
			}
			return ret;
		}
	};
	template<class K, class T>
	class HashNode
	{
	public:
		HashNode(const pair<K, T>& kv)
			:_kv(kv)
			,_next(nullptr)
		{}
		pair<K, T> _kv;
		HashNode* _next;
	};

	template<class K, class T, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		HashTable()
		{
			_tables.resize(10);
		}
		~HashTable()
		{
			//手动释放哈希桶
			//遍历哈希表依次释放桶中的节点
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}
	public:
		typedef HashNode<K, T> Node;
		Hash hf;  //仿函数用于转化整型

		//查找
		Node* Find(const K& key)
		{
			size_t hashi = hf(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
					return cur;
				else cur = cur->_next;
			}
			return nullptr;
		}
		//插入
		bool Insert(const pair<K, T>& kv)
		{
			if (Find(kv.first))
				return false;
			//扩容
			//负载因子为1再扩容
			if (_n == _tables.size())
			{
				//创建新的哈希表
				vector<Node*> newTables;
				newTables.resize(_tables.size() * 2, nullptr);
				//遍历旧表
				for (size_t i = 0; i < _tables.size(); i++)
				{
					//将旧表的节点依次遍历插入到新表
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;      //记录下一个节点
						size_t hashi = hf(cur->_kv.first) % newTables.size();  //找到在新表里面的映射
						cur->_next = newTables[hashi];  //链接
						newTables[hashi] = cur;
						cur = next;   //找下一个
					}
					_tables[i] = nullptr;  //断开链接
				}
				_tables.swap(newTables);   //拿到新表的数据
			}

			//头插
			size_t hashi = hf(kv.first) % _tables.size();
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			_n++;
			return true;
		}

		//删除
		bool Erase(const K& key)
		{
			size_t hashi = hf(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
						delete cur;
						return true;
					}
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}
	private:
		vector<Node*> _tables;
		size_t _n;
	};
}

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!    

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

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

相关文章

前端网络安全笔记

本文主要涉及6个内容&#xff1a; HTTP与HTTPS同源策略&#xff08;Same-origin policy&#xff0c;简称 SOP&#xff09;/ 跨域资源共享&#xff08;Cross-Origin Resource Sharing&#xff0c;简称 CORS&#xff09;跨站脚本攻击&#xff08;Cross-Site Scripting&#xff0…

Base64编码的优点与缺点

title: Base64编码的优点与缺点 date: 2024/2/16 14:06:37 updated: 2024/2/16 14:06:37 tags: Base64编码ASCII转换数据传输文本存储安全性数据膨胀字符串解码 Base64编码是一种将二进制数据转换为可打印ASCII字符的编码方式。它被广泛应用于数据传输和存储&#xff0c;以提升…

飞天使-k8s知识点18-kubernetes实操3-pod的生命周期

文章目录 探针的生命周期流程图prestop 探针的生命周期 docker 创建&#xff1a;在创建阶段&#xff0c;你需要选择一个镜像来运行你的应用。这个镜像可以是公开的&#xff0c;如 Docker Hub 上的镜像&#xff0c;也可以是你自己创建的自定义镜像。创建自己的镜像通常需要编写一…

基于Python实现的元宵节猜灯谜兑奖软件源码,输入灯谜序号,获取谜面及谜底

基于Python实现的元宵节猜灯谜兑奖软件源码&#xff0c;输入灯谜序号&#xff0c;获取谜面及谜底 核心代码&#xff1a; import sys from time import sleep import xlrd import os import tkintertable_listA "" table_listB ""filename os.getcwd()&…

重磅!谷歌宣布发布Gemini 1.5 Pro,距离Gemini发布仅仅一个半月!最高支持1000万上下文长度,GSM8K评测全球第一

本文原文来自DataLearnerAI官方网站&#xff1a; 重磅&#xff01;谷歌宣布发布Gemini 1.5 Pro&#xff0c;距离Gemini发布仅仅一个半月&#xff01;最高支持1000万上下文长度&#xff0c;GSM8K评测全球第一 | 数据学习者官方网站(Datalearner)https://www.datalearner.com/bl…

【题解】差分

差分其实就是前缀和的逆运算。 如果数组 A 是数组 B 的前缀和数组&#xff0c;则称 B 是 A 的差分数组。 思路 由题意得&#xff0c;应该求给定数组的差分数组。 差分加速的原理 对 L 到 R 区间内的数加上 c&#xff0c;时间复杂度是O(c) &#xff0c;即O(n) 。 但是如果…

c++阶梯之类与对象(下)

前文&#xff1a; c阶梯之类与对象&#xff08;上&#xff09;-CSDN博客 c阶梯之类与对象&#xff08;中&#xff09;-CSDN博客 c阶梯之类与对象&#xff08;中&#xff09;&#xff1c; 续集 &#xff1e;-CSDN博客 1. 再谈构造函数 1.1 构造函数体赋值 在创建对象时&a…

C# VS2022+WinForm+Oracle19.3+Excel,根据数据库表定义书生成SQL

目标&#xff1a; 用Excel写数据库的表的定义书&#xff0c;用该工具生成SQL&#xff0c;在客户端执行&#xff0c;把表结构导入数据库&#xff0c;生成真正的表 Github代码下载 目录 0.完成下面开发环境的准备1 操作系统Win11 专业版 21H22 oracle 19.33 Visual Studio Commun…

transformer-Attention is All You Need(一)

1. 为什么需要transformer 循环模型通常沿输入和输出序列的符号位置进行因子计算。通过在计算期间将位置与步骤对齐&#xff0c;它们根据前一步的隐藏状态和输入产生位置的隐藏状态序列。这种固有的顺序特性阻止了训练样本内的并行化&#xff0c;这在较长的序列长度上变得至关重…

Java中的Queue队列的基本讲解

目录 一、创建队列 二、Queue的一些常用方法 对于队列的概念我就不多说了吧&#xff0c;先进先出&#xff0c;比如1,2,3进入队列&#xff0c;出队列也是1,2,3。这里我主要说的是在Java中如何创建和使用队列。 一、创建队列 队列的创建&#xff0c;也可以说是队列的实例化。 Q…

精炼爆炸性新闻!OpenAI发布革命性AI视频生成模型Sora:实现长达60秒的高清视频创作「附AIGC行业系统搭建」

在人工智能领域&#xff0c;每一次技术革新都引领着未来的发展方向。OpenAI&#xff0c;作为全球领先的人工智能研究机构&#xff0c;再次证明了其在推动AI技术革新方面的领导地位。近日&#xff0c;OpenAI宣布推出了一款革命性的AI视频生成模型——Sora&#xff0c;这一大胆的…

Dart 3.3 发布:扩展类型、JavaScript Interop 等

参考链接&#xff1a;https://medium.com/dartlang/dart-3-3-325bf2bf6c13 跟随 Flutter 3.19 发布的还有 Dart 3.3 &#xff0c;Dart 3.3 主要包含扩展类型增强&#xff0c;性能优化和 native 代码交互推进&#xff0c;例如本次改进的JavaScript Interop 模型就引入了类型安全…

【lesson56】生产者消费者模型

文章目录 学习生产者消费者模型过程中要回答的两个问题生产者消费者模型的概念基于阻塞队列的生产者消费者模型编码实现Common.hLockGuard.hppCondtion.hppBlockQueue.hppTask.hppConProd.cc 学习生产者消费者模型过程中要回答的两个问题 1.条件变量是在条件满足的时候&#x…

使用 XML 和 YAML 文件的文件输入和输出

目标 您将找到以下问题的答案&#xff1a; 如何使用YAML或XML文件打印和读取文件和OpenCV的文本条目&#xff1f;如何对 OpenCV 数据结构做同样的事情&#xff1f;如何为您的数据结构执行此操作&#xff1f;使用 OpenCV 数据结构&#xff0c;例如 cv&#xff1a;&#xff1a;…

SolidWorks如何在一个零件的基础上绘制另一个零件

经过测试&#xff0c;新建零件&#xff0c;然后插入零件a&#xff0c;在a的基础上绘制b,这种做法无法断开a与b的联系。虽然可以通过切除命令&#xff0c;切除b&#xff0c;但不是正途。 在装配体中可以实现&#xff1a; &#xff08;1&#xff09;建立装配体 &#xff08;2&…

在线黑色响应式全屏滚动主页html源码

html5黑色大气的个人博客全屏滚动个人主页源码 右键记事本即可修改 直接上传服务器空间就可使用

【HTML】过年不能放烟花,那就放电子烟花

闲谈 大家回家过年可能都多多少少放过些&#x1f9e8;&#xff0c;但是有些在城市上过年的小伙伴可能就没有机会放鞭炮了。不过没关系&#xff0c;我们懂技术&#xff0c;我们用技术自娱自乐&#xff0c;放电子烟花&#xff0c;总不可能被警长叔叔敲门问候吧。 开干 首先&…

一个比SDXL更快的模型——Stable Cascade【必坑指北】

2024年的春节假期&#xff0c;AIGC界又发生了重大革命性事件。 OpenAI 发布了首款文生视频模型——Sora。简单来说就是&#xff0c;AI视频要变天了&#xff01;之前的SVD&#xff0c;还是Google的Lumiere最多就几十帧&#xff0c;大约十秒左右&#xff0c;但是Sora却是SOTA级别…

文生视频:Sora模型报告总结

作为世界模拟器的视频生成模型 我们探索视频数据生成模型的大规模训练。具体来说&#xff0c;我们在可变持续时间、分辨率和宽高比的视频和图像上联合训练文本条件扩散模型。我们利用对视频和图像潜在代码的时空补丁进行操作的变压器架构。我们最大的模型 Sora 能够生成一分钟…

BUGKU-WEB 网站被黑

题目描述 题目截图如下&#xff1a; 进入场景看看&#xff1a; 解题思路 提示说&#xff1a; 网站被黑了 黑客会不会留下后门&#xff08;那就是留了&#xff09;那就扫描目录先看看再说发现确实有登入界面&#xff0c;但是密码不知道&#xff08;爆破走起&#xff09; …