哈希和unordered系列封装(C++)

哈希和unordered系列封装

  • 一、哈希
    • 1. 概念
    • 2. 哈希函数,哈希碰撞
      • 哈希函数(常用的两个)
      • 哈希冲突(碰撞)
      • 小结
    • 3. 解决哈希碰撞
      • 闭散列
        • 线性探测
        • 二次探测
        • 代码实现
        • 载荷因子(扩容)
      • 开散列
        • 哈希桶
        • 代码实现
        • 扩容
  • 二、unordered系列封装
    • hash_table
      • 迭代器实现原理(单项迭代器)
      • hash_table实现代码
    • unordered_set封装
    • unordered_map封装
  • 三、总结

一、哈希

1. 概念

通过某种函数使用元素的存储位置与其关键码之间建立映射关系。

  1. 插入元素时,通过该函数求得的值,就是该元素的存储位置。
  2. 搜索元素时,通过该函数求得的值进行比对,如果关键码相等则搜索成功。

该方法称为哈希(散列)方法, 而其中的某中函数被称为哈希(散列)函数,构造出来的结构成为哈希表(散列表)。

2. 哈希函数,哈希碰撞

哈希函数(常用的两个)

直接定址法

  1. 函数
    取关键字的某个线性函数得出散列地址:Hash(Key) = A * Key + B
  2. 优缺
    优点:简单均匀
    缺点:关键码的分布范围需要集中
  3. 场景
    统计字符串中字符出现的个数,其中字符是集中的。

除留余数法

  1. 函数
    Hash(Key) = Key % m(m是小于等于表中可取地址数即可(建议:质数))
  2. 场景
    适用于值的方位分散

eg:
除留余数法

注意:

  1. 使用除留余数法,所以就要求被%的key必须是整型。如果key为字符串如何转成整型呢?
    答:字符串哈希函数。评价hash函数性能的一个重要指标就是冲突,在相关资源允许的条件下冲突越少hash函数的性能越好。
    常见的字符串哈希算法BKDRHash,APHash,DJBHash…

eg:

 // BKDR Hash Function
unsigned int BKDRHash(char *str)
{
    unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
    unsigned int hash = 0;
 
    while (*str)
    {
        hash = hash * seed + (*str++);
    }
 
    return (hash & 0x7FFFFFFF);
}
  1. 使用除留余数法,最好模一个素数,如何快速模一个类似两倍关系的素数?
    答:使用了一个默认的素数集合,这个集合中包含了一系列素数。在不同的STL实现中,这个素数集合可能会有所不同。一般来说,这个集合中的素数经过仔细选择,以确保哈希表的负载因子(即平均哈希桶中元素的数量)保持在一个较小的范围内,从而提供更好的性能。
//素数集合
size_t GetNextPrime(size_t prime)
{
	const int PRIMECOUNT = 28;
	static const size_t primeList[PRIMECOUNT] =
	{
		53ul, 97ul, 193ul, 389ul, 769ul,
		1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
		49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
		1572869ul, 3145739ul, 6291469ul, 12582917ul,
		25165843ul,
		50331653ul, 100663319ul, 201326611ul, 402653189ul,
		805306457ul,
		1610612741ul, 3221225473ul, 4294967291ul
	};
	size_t i = 0;
	for (; i < PRIMECOUNT; ++i)
	{
		if (primeList[i] > prime)
			return primeList[i];
	}
	return primeList[i];
}

哈希冲突(碰撞)

根据上面的例子,如果在数据集合中添加一个数据25,那么会发现通过哈希函数求的地址已经被别的关键码占据。

概念: 不同关键码通过相同的哈希函数计算出相同的哈希地址,被称为哈希冲突(碰撞)。

小结

哈希函数的设计跟哈希冲突有着必要的联系。
哈希函数的设计:

  1. 哈希函数的定义域,需要包含存储的全部关键码。值域,0到哈希表允许地址数最大值-1
  2. 哈希函数计算的地址,均匀分布在哈希表中
  3. 设计简单

3. 解决哈希碰撞

解决哈希碰撞的两种方法:闭散列和开散列

闭散列

闭散列:也叫开放地址法,当发生哈希冲突时,如果哈希表未被填满,说明哈希表还有空位置,那么就可以从冲突位置为起始找下一个空位置。

线性探测

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

优缺点

  1. 优点:实现简单
  2. 缺点:一旦发生冲突连在一起,容易产生数据“堆积”。搜索效率下降

插入

  • 通过哈希函数获取待插入元素在哈希表的目标位置
  • 如果该位置没有元素直接插入,如果有元素则发生冲突,使用线性探测找到下一个空位置,然后插入。

eg:
线性探测

删除

  • 因为哈希冲突的原因,不能随便删除,会影响后面元素的搜索。例如:删除上个例子哈希表的6,那么我们查找25会被影响。
  • 所以采用伪删除,给哈希表每个空间设置一个状态
    `状态: EMPTY此位置为空,EXIST此位置有元素,DELETE此位置元素被删除。
enum STATE
{
	EXIST, 
	EMPTY,
	DELETE
};`
二次探测

不同于线性探测是依次寻找空位置,二次探测是通过公式跳跃式的寻找空位置。
Hash(i) = (Hash(x) + i^2) % m;
Hash(X):通过哈希函数计算key值得到的位置,但是已经存在元素
Hash(i):将要存放位置
m:哈希表的大小
i = 1,2,3,4…

注意: 除了线性探测,二次探测,还有双重哈希…

代码实现
//开放地址法
namespace open_address 
{
	//哈希函数
	template<class K>
	struct DefaultHashFunc
	{
		size_t operator()(const K& key)
		{
			return size_t(key);     //转成无符号整型
		}
	};
	
	//模板特化 -- 针对字符串    BKDRHash算法
	template<>
	struct DefaultHashFunc<string>
	{
		size_t operator()(const string& s)
		{
			size_t hash = 0;
			for (auto ch : s)
			{
				hash *= 131;
				hash += ch;
			}
			return hash;
		}
	};


	//状态
	enum STATE
	{
		EXIST,
		EMPTY,
		DELETE
	};

	//数据
	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		STATE _state = EMPTY;
	};

	template<class K, class V, class HashFunc = DefaultHashFunc<K>>
	class HashTable
	{
	public:
		HashTable()
		{
			_table.resize(10);     //给哈希表初始化十个空间
		}

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

			//扩容   -->   根据载荷因子
			//if ((double)_n / (double)_table.size() >= 0.7)
			if (10 * _n / _table.size() >= 7)
			{
				size_t newSize = _table.size() * 2;
				
				//造新表
				HashTable<K, V, HashFunc> newHT;
				newHT._table.resize(newSize);

				//遍历旧表重新映射到新表
				for (size_t i = 0; i < _table.size(); i++)
				{
					if (_table[i]._state == EXIST)
					{
						newHT.Insert(_table[i]._kv);
					}
				}

				//交换新旧表,原空间出作用域自动销毁
				_table.swap(newHT._table);
			}

			//线性探测
			HashFunc hf;
			size_t hashi = hf(kv.first) % _table.size();
			while (_table[hashi]._state == EXIST)
			{
				++hashi;
				hashi %= _table.size();
			}
			_table[hashi]._kv = kv;
			_table[hashi]._state = EXIST;
			_n++;
			return true;
		}

		HashData<const K, V>* Find(const K& key)
		{
			HashFunc hf;
			size_t hashi = hf(key) % _table.size();
			while (_table[hashi]._state != EMPTY)
			{
				if (_table[hashi]._state == EXIST
					&& _table[hashi]._kv.first == key)
				{
					//&_table[hashi]类型是HashData<K, V>*
					return (HashData<const K, V>*)&_table[hashi];    
				}

				++hashi;
				//如果到_table的最后了,绕到最前面
				hashi %= _table.size();
			}
			return nullptr;
		}

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

	private:
		vector<HashData<K, V>> _table;
		size_t _n = 0;           //存储有效数据
	};

}
载荷因子(扩容)

载荷因子的就算方法:α = 表中有效的元素个数 / 散列表的长度。
对于开放地址法,载荷因子是特别重要的元素,通过一些科学实验,载荷因子应严格控制在0.7-0.8。∵散列表的长度是一定的,表中有效元素个数和α成正比,∴如果超过载荷因子0.8,产生冲突的可能就越大,查表时CPU缓存命中率低。

再进行插入操作的时候要根据载荷因子判断需不需要扩容,用空间换时间

开散列

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

哈希桶

哈希桶

5和8下标都存在哈希冲突

代码实现
namespace hash_bucket
{
	template<class K>
	struct DefaultHashFunc
	{
		size_t operator()(const K& key)
		{
			return size_t(key);
		}
	};
	//模板特化 -- 针对字符串
	template<>
	struct DefaultHashFunc<string>
	{
		size_t operator()(const string& s)
		{
			size_t hash = 0;
			for (auto ch : s)
			{
				hash *= 131;
				hash += ch;
			}

			return hash;
		}
	};


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

		//初始化
		HashNode(const pair<K, V>& kv)
			:_kv(kv)
			,_next(nullptr)
		{}
	};


	template<class K, class V, class HashFunc = DefaultHashFunc<K>>
	class HashTable
	{
		typedef HashNode<const K, V> Node;
	public:
		HashTable()
		{
			//开十个空间,初始化为nullptr
			_table.resize(10, nullptr);
		}

		~HashTable()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
		}

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
			{
				return false;
			}
			HashFunc hf;

			//负载因子到1扩容
			if (_n == _table.size())
			{
				size_t newSize = _table.size() * 2;
				vector<Node*> newTable;
				newTable.resize(newSize, nullptr);

				//遍历旧表
				for (size_t i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hashi = hf(cur->_kv.first) % newSize;
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;

						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newTable);
			}

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

		Node* Find(const K& key)
		{
			HashFunc hf;
			size_t hashi = hf(key) % _table.size();
			Node* cur = _table[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

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

					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

		void Print()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				printf("[%d]->", i);
				Node* cur = _table[i];
				while (cur)
				{
					cout << cur->_kv.first << ":" << cur->_kv.second << "->";
					cur = cur->_next;
				}
				printf("nullptr\n");
			}
			cout << endl;
		}

	private:
		vector<Node*> _table;
		size_t _n = 0;
	};
}
扩容

桶的个数是一定的(桶的个数 == 表的大小)。如果不进行扩容,可能一个桶中有很多元素,会影响哈希表的性能。开散列最完美的情况就是每个哈希桶中刚好挂一个节点,再插入时就会发生哈希冲突,因此判断扩容的条件就可以是: 元素的个数 == 桶的个数

二、unordered系列封装

unordered系列set、map的容器接口和红黑树实现的set、map相似,使用大差不差,所以在这里就不进行介绍了。

hash_table

迭代器实现原理(单项迭代器)

  1. 迭代器++
  1. 当前桶没遍历完,直接通过链表找下一个节点
  2. 当前桶遍历完
    a. 通过哈希函数确定当前存储位置然后+1
    b. 循环(加过1的位置小于哈希表的大小)
    - - Ⅰ.该位置不为空,则成功找到,直接返回
    - - Ⅱ.该位置为空继续向后+1,继续循环判断
    c. 循环结束没找到,返回nullptr
Self& operator++()
{
	if (_node->_next)  //当前桶没完
	{
		_node = _node->_next;
	}
	else               //当前桶完了
	{
		HashFunc hf;
		KeyOfT kot;

		size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
		++hashi;
		while (hashi < _pht->_table.size())
		{
			if (_pht->_table[hashi])
			{
				_node = _pht->_table[hashi];
				return *this;
			}
			else
			{
				hashi++;
			}
		}
		_node = nullptr;
	}
	return *this;
}

hash_table实现代码

#include <vector>

// 1、哈希表
// 2、封装map和set
// 3、普通迭代器
// 4、const迭代器
// 5、insert返回值  operator[]
// 6、key不能修改的问题

namespace hash_bucket
{
	template<class K>
	struct DefaultHashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

	template<>   //特化
	struct DefaultHashFunc<string>
	{
		size_t operator()(const string& str)
		{
			size_t hash = 0;
			for (auto ch : str)
			{
				hash *= 131;
				hash += ch;
			}
			return hash;
		}
	};


	template<class T>
	struct HashNode
	{
		T _data;
		HashNode<T>* _next;

		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{}
	};

	//类前置声明  -->  因为迭代器的实现会调用哈希表指针
	template<class K, class T, class KeyOfT, class HashFunc>
	class HashTable;


	//迭代器
	template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
	struct HTIterator
	{
		typedef HashNode<T> Node;
		typedef HTIterator<K, T, Ptr, Ref, KeyOfT, HashFunc> Self;

		//普通迭代器
		typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> Iterator;

		Node* _node;
		//哈希表指针  注意这里要加上const限制*this,不然哈希表调用时的this是const的会导致权限放大
		const HashTable<K, T, KeyOfT, HashFunc>* _pht;
		
		HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
			:_node(node)
			,_pht(pht)
		{}

		//普通迭代器时,是拷贝构造
		//const迭代器时,是构造。普通迭代器构造const迭代器
		HTIterator(const Iterator& it)
			:_node(it._node)
			, _pht(it._pht)
		{}

		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}

		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}

		bool operator==(const Self& s)
		{
			return _node == s._node;
		}

		Self& operator++()
		{
			if (_node->_next)  //当前桶没完
			{
				_node = _node->_next;
			}
			else               //当前桶完了
			{
				HashFunc hf;
				KeyOfT kot;

				size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
				++hashi;
				while (hashi < _pht->_table.size())
				{
					if (_pht->_table[hashi])
					{
						_node = _pht->_table[hashi];
						return *this;
					}
					else
					{
						hashi++;
					}
				}
				_node = nullptr;
			}
			return *this;
		}
	};


	//set -> hash_bucket::HashTable<K, K> _ht
	//map -> hash_bucket::HashTable<K, pair<K, V>> _ht
	template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
	class HashTable
	{
		typedef HashNode<T> Node;

		//友元         迭代器的实现会调用哈希表指针
		template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
		friend struct HTIterator;

	public:

		typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> iterator;
		typedef HTIterator<K, T, const T*, const T&, KeyOfT, HashFunc> const_iterator;

		iterator begin()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				if (cur)
				{
					return iterator(cur, this);
				}
			}

			return iterator(nullptr, this);
		}

		iterator end()
		{
			return iterator(nullptr, this);
		}


		const_iterator begin()  const
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				if (cur)
				{
					return const_iterator(cur, this);
				}
			}

			return const_iterator(nullptr, this);
		}

		const_iterator end() const
		{
			return const_iterator(nullptr, this);
		}



		HashTable()
		{
			_table.resize(10, nullptr);
		}

		~HashTable()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}

				_table[i] = nullptr;
			}
		}

		pair<iterator, bool> Insert(const T& data)
		{
			HashFunc hf;
			KeyOfT kot;

			iterator it = Find(kot(data));


			if (it != end())
			{
				return make_pair(it, false);
			}


			// 负载因子到1--扩容
			if (_n == _table.size())
			{
				size_t newSize = _table.size() * 2;
				vector<Node*> newTable;
				newTable.resize(newSize, nullptr);

				// 遍历旧表,把节点牵下来挂到新表
				for (size_t i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hf(kot(data)) % newSize;
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;

						cur = next;
					}

					_table[i] = nullptr;
				}

				_table.swap(newTable);
			}

			size_t hashi = hf(kot(data)) % _table.size();
			// 头插
			Node* newnode = new Node(data);
			newnode->_next = _table[hashi];
			_table[hashi] = newnode;
			++_n;
			return make_pair(iterator(newnode, this), true);
		}

		iterator Find(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;

			size_t hashi = hf(key) % _table.size();
			Node* cur = _table[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}

				cur = cur->_next;
			}

			return iterator(nullptr, this);
		}

		bool Erase(const K& key)
		{
			HashFunc hf;
			KeyOfT kot;

			size_t hashi = hf(key) % _table.size();
			Node* prev = nullptr;
			Node* cur = _table[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr)
					{
						_table[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}

					delete cur;
					return true;
				}

				prev = cur;
				cur = cur->_next;
			}

			--_n;
			return false;
		}

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

unordered_set封装

namespace kpl
{
	template<class K>
	class unordered_set
	{
		//该仿函数只是跟map跑
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;


		const_iterator begin() const
		{
			return _ht.begin();
		}

		const_iterator end() const
		{
			return _ht.end();
		}


		pair<iterator, bool> insert(const K& key)
		{
			//这里返回值的first的迭代器是普通迭代器,用普通迭代器接收
			pair<typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);
			//使用普通迭代器构造一个const的迭代器,这里就体现出迭代器实现中的那个拷贝构造
			return pair<iterator, bool>(ret.first, ret.second);
		}

	private:
		hash_bucket::HashTable<K, K, SetKeyOfT> _ht;
	};
}

unordered_map封装

namespace kpl
{
	template<class K, class V>
	class unordered_map
	{
		//仿函数的主要作用在这里,set的封装只是跟跑,为了就是去键值对的key
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

		iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}

		const_iterator begin() const
		{
			return _ht.begin();
		}

		const_iterator end() const
		{
			return _ht.end();
		}

		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}
	
		//返回值是与key对应的value的值。
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
			return ret.first->second;
		}

	private:
		hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT> _ht;
	};

}

三、总结

闭散列的缺陷:空间利用率低,用空间换时间,这也是哈希缺陷
开散列和闭散列的区别:
链地址法比开地址法更加的节省存储空间。原因:虽然链地址法增加了连接指针,但是开地址法为了保证搜索效率,必须保持大量的空闲空间。

unordered_set和unordered_map

  1. 前向迭代器
  2. 遍历出来不是有序
  3. 通过key访问当个元素的效率比set和map快,遍历元素子集的范围迭代方面效率较低

其余的特征和set,map大差不差,可以参考我的上一篇博客

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

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

相关文章

订单管理系统怎么用?有哪些好用的订单管理系统?

订单管理系统怎么用&#xff1f;有哪些好用的订单管理系统&#xff1f;阅读本文你将了解&#xff1a;1、订单管理系统的核心功能&#xff1b;2、订单管理系统的拓展功能&#xff1b;3、订单管理系统推荐。 订单管理系统在当今商业环境中扮演着至关重要的角色。它是企业内部运营…

UE4 UE5 使用SVN控制

关键概念&#xff1a;虚幻引擎中使用SVN&#xff0c;帮助团队成员共享资源。 1. UE4/UE5项目文件 如果不需要编译的中间缓存&#xff0c;则删除&#xff1a; DerivedDataCache、Intermediate、Saved 三个文件夹 2.更新、上传

GAN:GAN论文学习

论文&#xff1a;https://arxiv.org/pdf/1406.2661.pdf 发表&#xff1a;2014 一、GAN简介&#xff1a;Generative Adversarial Network GAN是由Ian Goodfellow于2014年提出&#xff0c;GAN&#xff1a;全名叫做生成对抗网络。GAN的目的就是无中生有&#xff0c;以假乱真。 …

解决Maven项目jar包下载失败的问题

文章目录 配置国内的Maven源引入正确的settings.xml文件重新下载jar包对后面要创建的新项目也统一配置仍然失败的解决办法 配置国内的Maven源 引入正确的settings.xml文件 如果该目录下的 settings.xml文件不存在或者错误&#xff0c;要创建一个 settings.xml文件并写入正确的…

【代码】基于卷积神经网络(CNN)-支持向量机(SVM)的分类预测算法

程序名称&#xff1a;基于卷积神经网络&#xff08;CNN&#xff09;-支持向量机&#xff08;SVM&#xff09;的分类预测算法 实现平台&#xff1a;matlab 代码简介&#xff1a;CNN-SVM是一种常用的图像分类方法&#xff0c;结合了卷积神经网络&#xff08;CNN&#xff09;和支…

Java 基础学习(二)运算符与分支流程控制

1 运算符 1.1 运算符概述 1.1.1 运算符概述 运算符是一种告诉计算机执行特定的数学或逻辑等操作的符号。Java运算符号包括&#xff1a;数学运算符、关系运算符、逻辑运算符、赋值运算符号、字符串连接运算符。计算机本质上只能处理数字&#xff0c;处理数字的最常见的方式就…

使用std::mutext与std::condition_variables实现信号量

1. 信号量的定义 2. 使用std::mutext与std::condition_variables实现信号量 代码来自&#xff1a;https://zhuanlan.zhihu.com/p/462668211 #ifndef _SEMAPHORE_H #define _SEMAPHORE_H #include <mutex> #include <condition_variable> using namespace std;cla…

【算法】20231128

这里写目录标题 一、55. 跳跃游戏二、274. H 指数三、125. 验证回文串 一、55. 跳跃游戏 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&am…

yolov8-seg 分割推理流程

目录 一、分割检测 二、图像预处理 二、推理 三、后处理与可视化 3.1、后处理 3.2、mask可视化 四、完整pytorch代码 一、分割检测 注&#xff1a;本篇只是阐述推理流程&#xff0c;tensorrt实现后续跟进。 yolov8-pose的tensorrt部署代码稍后更新&#xff0c;还是在仓…

git的创建以及使用

1、上传本地仓库 首先确定项目根目录中没有.git文件&#xff0c;有的话就删了&#xff0c;没有就下一步。在终端中输入git init命令。注意必须是根目录&#xff01; 将代码存到暂存区 将代码保存到本地仓库 2、创建git仓库 仓库名称和路径&#xff08;name&#xff09;随便写…

4P营销模型

4P营销模型 菲利普科特勒在其畅销书《营销管理&#xff1a;分析、规划与控制》中进一步确认了以4P为核心的营销组合方法. 模型介绍 「4P营销模型」是市场营销中的经典理论&#xff0c;代表了产品、价格、促销和渠道四个要素。这些要素是制定市场营销策略和实施计划的关键组成部…

MySql的InnoDB的三层B+树可以存储两千万左右条数据的计算逻辑

原创/朱季谦 B树是一种在非叶子节点存放排序好的索引而在叶子节点存放数据的数据结构&#xff0c;值得注意的是&#xff0c;在叶子节点中&#xff0c;存储的并非只是一行表数据&#xff0c;而是以页为单位存储&#xff0c;一个页可以包含多行表记录。非叶子节点存放的是索引键…

SA与NSA网络架构的区别

SA与NSA网络架构的区别 1. 三大运营商网络制式&#xff1a;2. 5G组网方式及业务特性3. NSA-3系列4. NSA—4系列5. NSA-7系列6. 5G SA网络架构7. 运营商策略 1. 三大运营商网络制式&#xff1a; 联通&#xff1a;3G(WCDMA)\4G(FDD-LTE/TD-LTE)\5G(SA/NSA)移动&#xff1a;2G(GS…

健全隧道健康监测,保障隧道安全管理

隧道工程事故的严重性不容忽视。四川隧道事故再次凸显了隧道施工的危险性&#xff0c;以及加强隧道安全监管的必要性。隧道工程事故不仅会给受害人带来巨大的痛苦和家庭悲剧&#xff0c;也会对整个社会产生严重的负面影响。因此&#xff0c;如何有效地降低隧道工程事故的发生率…

开发知识点-CSS样式

CSS样式 fontCSS 外边距 —— 围绕在元素边框的空白区域# linear-gradient() ——创建一个线性渐变的 "图像"# transform ——旋转 元素![在这里插入图片描述](https://img-blog.csdnimg.cn/20191204100321698.png)# rotate() [旋转] # 边框 (border) —— 围绕元素内…

Peter算法小课堂—高精度减法

给大家看个小视频高精度减法_哔哩哔哩_bilibili 基本思想 计算机模拟人类做竖式计算&#xff0c;从而得到正确答案 大家还记得小学时学的“减法竖式”吗&#xff1f;是不是这样 x-y问题 函数总览&#xff1a; 1.converts() 字符串转为高精度大数 2.le() 判断大小 3.sub() …

无分类编址 CIDR

在域名系统出现之后的第一个十年里&#xff0c;基于分类网络进行地址分配和路由IP数据包的设计就已明显显得可扩充性不足&#xff08;参见RFC 1517&#xff09;。为了解决这个问题&#xff0c;互联网工程工作小组在1993年发布了一新系列的标准——RFC 1518和RFC 1519——以定义…

Git分支管理--Bug分支

愿所有美好如期而遇 我们现在正在dev4分支上进行开发&#xff0c;但是在我们开发过程中&#xff0c;并且我们还未提交&#xff0c;master分支上出现了bug&#xff0c;需要我们修复&#xff0c;我们先来看情景 我们添加一行代码并且不提交充作开发&#xff0c;模拟正在进行开发时…

面试题:汉诺塔问题 · 递归

你好&#xff0c;我是安然无虞。 文章目录 汉诺塔问题问题描述解题思路代码详解 汉诺塔问题 问题描述 解题思路 这道题的名字还是很响的&#xff0c;基本上都能看出来使用递归解题&#xff0c;但是具体怎么实现还是需要细细想一想。 我们一步一步来&#xff0c;请看&#xff…

【搜维尔科技】产品推荐:Virtuose 6D RV,大型工作空间触觉设备

Virtuose 6D RV为一款具有大工作空间并在所有6自由度上提供力反馈的触觉设备&#xff0c;设计专用于虚拟现实环境&#xff0c;特别适合于大型虚拟物体的处理。 Virtuose 6D RV是当今市场上唯一将高工作效率与高工作量相结合在一起的产品。6D RV特别适合于缩放与操纵等应用&…