哈希(模拟实现unordered系列容器)

目录

  • 哈希表源代码
  • 哈希表模板参数的控制
    • 哈希表区分
    • set与map的不同模板参数
      • 哈希节点定义的模板参数修改
    • 提供仿函数,获取T类型数据当中的键值
      • unordered_map的仿函数
      • unordered_set的仿函数
      • 哈希表的模板参数增加
  • string类型无法取模问题
    • 哈希表的模板参数增加
  • 哈希表默认成员函数实现
    • 构造函数
    • 拷贝构造函数
    • 赋值运算符重载函数
    • 析构函数
  • 哈希表正向迭代器的实现
    • 构造函数
    • operator*
    • operator->
    • operator!= && operator==
    • operator++
  • 哈希表结构的其他实现方式
  • unordered_set的模拟实现
  • unordered_map的模拟实现
  • 封装完成后的完整代码如下
    • 哈希表的代码
    • 正向迭代器的代码
    • unordered_set && unordered_map

哈希表源代码

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 Hash = HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public:HashTable(){_tables.resize(10, nullptr);}~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;}}bool Insert(const pair<K, V>& kv){Hash hs;size_t hashi = hs(kv.first) % _tables.size();// 负载因子==1扩容if (_n == _tables.size()){/*HashTable<K, V> newHT;newHT._tables.resize(_tables.size() * 2);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while(cur){newHT.Insert(cur->_kv);cur = cur->_next;}}_tables.swap(newHT._tables);*/vector<Node*> newtables(_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 = hs(cur->_kv.first) % newtables.size();// 头插到新表cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}// 头插Node* newnode = new Node(kv);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}Node* Find(const K& key){Hash hs;size_t hashi = hs(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 hs;size_t hashi = hs(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;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private://vector<list<pair<K, V>>> _tables;  // 指针数组//struct Bucket//{//	list<pair<K, V>> _lt;//	map<K, V> _m;//	size_t _bucketsize;   // >8 map  <=8 list//};//vector<Bucket> _tables;vector<Node*> _tables;  // 指针数组size_t _n = 0;			// 表中存储数据个数};//测试:
void TestHT1(){HashTable<int, int> ht;int a[] = { 11,21,4,14,24,15,9,19,29,39 };for (auto e : a){ht.Insert({ e,e });}ht.Insert({ -6, 6 });for (auto e : a){ht.Erase(e);}}void TestHT2(){HashTable<string, string> ht;ht.Insert({ "sort", "排序" });ht.Insert({ "left", "左边" });}

哈希表模板参数的控制

需要明确的是,unordered_set是K模型的容器,而unordered_map是KV模型的容器。

要想只用一份哈希表代码同时封装出K模型和KV模型的容器,我们必定要对哈希表的模板参数进行控制

哈希表区分

首先为了与原哈希表的模板参数进行区分,这里将哈希表的第二个模板参数的名字改为T。

template<class K, class T>
class HashTable

set与map的不同模板参数

其次,如果上层使用的是unordered_set容器,那么传入哈希表的模板参数就是key和key

template<class K>
class unordered_set
{
public://...
private:HashTable<K, K> _ht; //传入底层哈希表的是K和K
};

但如果上层使用的是unordered_map容器,那么传入哈希表的模板参数就是key以及key和value构成的键值对

template<class K, class V>
class unordered_map
{
public://...
private:HashTable<K, pair<K, V>> _ht; //传入底层哈希表的是K以及K和V构成的键值对
};

也就是说,哈希表中的模板参数T的类型到底是什么,完全却决于上层所使用容器的种类
在这里插入图片描述
而哈希结点的模板参数也应该由原来的K、V变为T:

上层容器是unordered_set时,传入的T是键值,哈希结点中存储的就是键值
上层容器是unordered_map时,传入的T是键值对,哈希结点中存储的就是键值对

哈希节点定义的模板参数修改

更改模板参数后,哈希结点的定义如下:

//哈希结点的定义
template<class T>
struct HashNode
{T _data;HashNode<T>* _next;//构造函数HashNode(const T& data):_data(data), _next(nullptr){}
};

提供仿函数,获取T类型数据当中的键值

在哈希映射过程中,我们需要获得元素的键值,然后通过哈希函数计算出对应的哈希地址进行映射

现在由于我们在哈希结点当中存储的数据类型是T,这个T可能就是一个键值,也可能是一个键值对,对于底层的哈希表来说,它并不知道哈希结点当中存储的数据究竟是什么类型,因此需要由上层容器提供一个仿函数,用于获取T类型数据当中的键值。

unordered_map的仿函数

因此,unordered_map容器需要向底层哈希表提供一个仿函数,该仿函数返回键值对当中的键值。

template<class K, class V>
class unordered_map
{//仿函数struct MapKeyOfT{const K& operator()(const pair<K, V>& kv) //返回键值对当中的键值key{return kv.first;}};
public://...
private:HashTable<K, pair<K, V>, MapKeyOfT> _ht;
};

unordered_set的仿函数

而虽然unordered_set容器传入哈希表的T就是键值,但是底层哈希表并不知道上层容器的种类,底层哈希表在获取键值时会统一通过传入的仿函数进行获取,因此unordered_set容器也需要向底层哈希表提供一个仿函数。

template<class K>
class unordered_set
{//仿函数struct SetKeyOfT{const K& operator()(const K& key) //返回键值key{return key;}};
public://...
private:HashTable<K, K, SetKeyOfT> _ht;
};

哈希表的模板参数增加

因此,底层哈希表的模板参数现在需要增加一个,用于接收上层容器提供的仿函数

template<class K, class T, class KeyOfT>
class HashTable

string类型无法取模问题

字符串并不是整型,也就意味着字符串不能直接用于计算哈希地址,我们需要通过某种方法将字符串转换成整型后,才能代入哈希函数计算哈希地址。

哈希表的模板参数增加

现在我们需要在哈希表的模板参数中再增加一个仿函数,用于将键值key转换成对应的整型

template<class K, class V, class Hash = HashFunc<K>>
class HashTable

用字符串作为键值key是比较常见的,因此我们可以针对string类型写一个类模板的特化

仿函数与特化版本代码如下:

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};// 特化
template<>
struct HashFunc<string>
{size_t operator()(const string& key){size_t hash = 0;for (auto e : key){hash *= 31;hash += e;}return hash;}
};

哈希表默认成员函数实现

构造函数

哈希表中有两个成员变量,当我们实例化一个对象时:

_table会自动调用vector的默认构造函数进行初始化。
_n会根据我们所给的缺省值被设置为0。

vector<Node*> _tables;  // 指针数组
size_t _n = 0;			// 表中存储数据个数

因此我们不需要编写构造函数,使用默认生成的构造函数就足够了,但是由于我们后面需要编写拷贝构造函数,编写了拷贝构造函数后,默认的构造函数就不会生成了,此时我们需要使用default关键字显示指定生成默认构造函数。

//构造函数
HashTable() = default; //显示指定生成默认构造函数

或者

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

拷贝构造函数

哈希表在拷贝时需要进行深拷贝,否则拷贝出来的哈希表和原哈希表中存储的都是同一批结点。

哈希表的拷贝构造函数实现逻辑如下:

  1. 将哈希表的大小调整为ht._tables的大小。
  2. 将ht._table每个桶当中的结点一个个拷贝到自己的哈希表中。
  3. 更改哈希表当中的有效数据个数。
//拷贝构造函数
HashTable(const HashTable& ht)
{//1、将哈希表的大小调整为ht._table的大小_tables.resize(ht._tables.size());//2、将ht._table每个桶当中的结点一个个拷贝到自己的哈希表中(深拷贝)for (size_t i = 0; i < ht._tables.size(); i++){if (ht._tables[i]) //桶不为空{Node* cur = ht._tables[i];while (cur) //将该桶的结点取完为止{Node* copy = new Node(cur->_data); //创建拷贝结点//将拷贝结点头插到当前桶copy->_next = _tables[i];_tables[i] = copy;cur = cur->_next; //取下一个待拷贝结点}}}//3、更改哈希表当中的有效数据个数_n = ht._n;
}

赋值运算符重载函数

实现赋值运算符重载函数时,可以通过参数间接调用拷贝构造函数,之后将拷贝构造出来的哈希表和当前哈希表的两个成员变量分别进行交换即可,当赋值运算符重载函数调用结束后,拷贝构造出来的哈希表会因为出了作用域而被自动析构,此时原哈希表之前的数据也就顺势被释放了。

//赋值运算符重载函数
HashTable& operator=(HashTable ht)
{//交换哈希表中两个成员变量的数据_tables.swap(ht._table);swap(_n, ht._n);return *this; //支持连续赋值
}

析构函数

因为哈希表当中存储的结点都是new出来的,因此在哈希表被析构时必须进行结点的释放。在析构哈希表时我们只需要依次取出非空的哈希桶,遍历哈希桶当中的结点并进行释放即可。

//析构函数
~HashTable()
{//将哈希表当中的结点一个个释放for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]) //桶不为空{Node* cur = _tables[i];while (cur) //将该桶的结点取完为止{Node* next = cur->_next; //记录下一个结点delete cur; //释放结点cur = next;}_tables[i] = nullptr; //将该哈希桶置空}}
}

哈希表正向迭代器的实现

哈希表的正向迭代器实际上就是对哈希结点指针进行了封装,但是由于在实现++运算符重载时,可能需要在哈希表中去寻找下一个非空哈希桶,因此每一个正向迭代器中都应该存储哈希表的地址。

template<class K, class T, class KeyOfT, class Hash>
struct HTIterator
{typedef HashNode<T> Node;//哈希结点的类型typedef HTIterator<K, T, Ptr, Ref, KeyOfT, Hash> Self;//正向迭代器的类型Node* _node;//结点指针const HashTable<K, T, KeyOfT, Hash>* _pht;//哈希表的地址
}

构造函数

因此在构造正向迭代器时,我们不仅需要对应哈希结点的指针,还需要该哈希结点所在哈希表的地址。

HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht):_node(node),_pht(pht)
{}

operator*

当对正向迭代器进行解引用操作时,我们直接返回对应结点数据的有引用即可。

T& operator*()
{return _node->_data; //返回哈希结点中数据的引用
}

operator->

当对正向迭代器进行->操作时,我们直接返回对应结点数据的地址即可。

T* operator->()
{return &_node->_data; //返回哈希结点中数据的地址
}

后续需要封装const迭代器,需要在给一个class Ref,class Ptr,class T是给HashTable用的
因此上述两个操作的返回值类型需要修改

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

模板需要更新

template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>

operator!= && operator==

我们需要比较两个迭代器是否相等时,只需要判断这两个迭代器所封装的结点是否是同一个即可。

bool operator!=(const Self& s) const
{return _node != s._node; //判断两个结点的地址是否不同
}bool operator==(const Self& s) const
{return _node == s._node; //判断两个结点的地址是否相同
}

operator++

++运算符重载函数的实现逻辑并不是很难,我们只需要知道如何找到当前结点的下一个结点即可。

  • 若当前结点不是当前哈希桶中的最后一个结点,则++后走到当前哈希桶的下一个结点。
  • 若当前结点是当前哈希桶的最后一个结点,则++后走到下一个非空哈希桶的第一个结点。
Self& operator++()
{if (_node->_next){// 当前桶还有节点_node = _node->_next;}else{// 当前桶走完了,找下一个不为空的桶KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();++hashi;while (hashi < _pht->_tables.size()){if (_pht->_tables[hashi]){break;}++hashi;}if (hashi == _pht->_tables.size()){_node = nullptr; // end()}else{_node = _pht->_tables[hashi];}}return *this;
}

注意: 哈希表的迭代器类型是单向迭代器,没有反向迭代器,即没有实现–运算符的重载,若是想让哈希表支持双向遍历,可以考虑将哈希桶中存储的单链表结构换为双链表结构。

哈希表结构的其他实现方式

在其他地方可能将插入哈希表的哈希结点统一链接到同一个单链表上,此时实现哈希表的正向迭代器时就更简单了,实现++运算符重载时,想要找到下一个结点就直接通过当前结点就可以找到。
在这里插入图片描述
正向迭代器实现后,我们需要在哈希表的实现当中进行如下操作:

  1. 进行正向迭代器类型的typedef,需要注意的是,为了让外部能够使用typedef后的正向迭代器类型iterator,我们需要在public区域进行typedef。
  2. 由于正向迭代器中++运算符重载函数在寻找下一个结点时,会访问哈希表中的成员变量_table,而_table成员变量是哈希表的私有成员,因此我们需要将正向迭代器类声明为哈希表类的友元。
  3. 将哈希表中查找函数返回的结点指针,改为返回由结点指针和哈希表地址构成的正向迭代器。
  4. 将哈希表中插入函数的返回值类型,改为由正向迭代器类型和布尔类型所构成的键值对。
    然后我们就可以在哈希表中实现迭代器相关的成员函数了:
  • begin函数: 返回哈希表中第一个非空哈希桶中的第一个结点的正向迭代器。
  • end函数: 返回空指针的正向迭代器。
//哈希表的实现
template<class K, class T, class KeyOfT, class HashFunc = Hash<K>>
class HashTable
{//将正向迭代器类声明为哈希表类的友元template<class K, class T, class KeyOfT, class HashFunc>friend struct __HTIterator;typedef HashNode<T> Node; //哈希结点类型
public:typedef __HTIterator<K, T, KeyOfT, HashFunc> iterator; //正向迭代器的类型iterator begin(){size_t i = 0;while (i < _table.size()) //找到第一个非空哈希桶{if (_table[i]) //该哈希桶非空{return iterator(_table[i], this); //返回该哈希桶中的第一个结点的正向迭代器}i++;}return end(); //哈希桶中无数据,返回end()}iterator end(){return iterator(nullptr, this); //返回nullptr的正向迭代器}
private:vector<Node*> _table; //哈希表size_t _n = 0; //哈希表中的有效元素个数
};

unordered_set的模拟实现

实现unordered_set的各个接口时,就只需要调用底层哈希表对应的接口就行了。

template<class K, class Hash = HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator 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 K& key){return _ht.Insert(key);}iterator Find(const K& key){return _ht.Find(key);}bool Erase(const K& key){return _ht.Erase(key);}private:hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;};

unordered_map的模拟实现

template<class K, class V, class Hash = HashFunc<K>>class unordered_map{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, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator 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);}V& operator[](const K& key){pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}iterator Find(const K& key){return _ht.Find(key);}bool Erase(const K& key){return _ht.Erase(key);}private:hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;};

封装完成后的完整代码如下

哈希表的代码

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};// 特化
template<>
struct HashFunc<string>
{size_t operator()(const string& key){size_t hash = 0;for (auto e : key){hash *= 31;hash += e;}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 Hash = HashFunc<K>>class HashTable{// 友元声明template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash = HashFunc<K>>friend struct HTIterator;typedef HashNode<T> Node;public:typedef HTIterator<K, T, T*, T&, KeyOfT, Hash> Iterator;typedef HTIterator<K, T, const T*, const T&, KeyOfT, Hash> ConstIterator;Iterator Begin(){if (_n == 0)return End();for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return Iterator(cur, this);}}return End();}Iterator End(){return Iterator(nullptr, this);}ConstIterator Begin() const{if (_n == 0)return End();for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return ConstIterator(cur, this);}}return End();}ConstIterator End() const{return ConstIterator(nullptr, this);}HashTable(){_tables.resize(10, nullptr);}~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;}}pair<Iterator, bool> Insert(const T& data){KeyOfT kot;Iterator it = Find(kot(data));if (it != End())return make_pair(it, false);Hash hs;size_t hashi = hs(kot(data)) % _tables.size();// 负载因子==1扩容if (_n == _tables.size()){/*HashTable<K, V> newHT;newHT._tables.resize(_tables.size() * 2);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while(cur){newHT.Insert(cur->_kv);cur = cur->_next;}}_tables.swap(newHT._tables);*/vector<Node*> newtables(_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 = hs(kot(cur->_data)) % newtables.size();// 头插到新表cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}// 头插Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(Iterator(newnode, this), true);}Iterator Find(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return Iterator(cur, this);}cur = cur->_next;}return End();}bool Erase(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables .size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private://vector<list<pair<K, V>>> _tables;  // 指针数组//struct Bucket//{//	list<pair<K, V>> _lt;//	map<K, V> _m;//	size_t _bucketsize;   // >8 map  <=8 list//};//vector<Bucket> _tables;vector<Node*> _tables;  // 指针数组size_t _n = 0;			// 表中存储数据个数};

正向迭代器的代码

// 前置声明template<class K, class T, class KeyOfT, class Hash>class HashTable;template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>struct HTIterator{typedef HashNode<T> Node;typedef HTIterator<K, T, Ptr, Ref, KeyOfT, Hash> Self;Node* _node;const HashTable<K, T, KeyOfT, Hash>* _pht;HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht):_node(node),_pht(pht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}Self& operator++(){if (_node->_next){// 当前桶还有节点_node = _node->_next;}else{// 当前桶走完了,找下一个不为空的桶KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();++hashi;while (hashi < _pht->_tables.size()){if (_pht->_tables[hashi]){break;}++hashi;}if (hashi == _pht->_tables.size()){_node = nullptr; // end()}else{_node = _pht->_tables[hashi];}}return *this;}};

unordered_set && unordered_map

与上面模拟实现相同

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

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

相关文章

探索天穹数仓自治能力的新实践

探索天穹数仓自治能力的新实践 随着业务和技术的发展&#xff0c;传统数仓模式向数智数仓模式演进&#xff0c;数据治理面临诸多挑战。自治平台采用双引擎策略&#xff0c;注重感知能力、观测能力、诊断能力和优化能力的建设&#xff0c;实现了对数据的精细化管理。例如&#x…

这本vue3编译原理开源电子书,初中级前端竟然都能看懂

前言 众所周知vue提供了很多黑魔法&#xff0c;比如单文件组件(SFC)、指令、宏函数、css scoped等。这些都是vue提供的开箱即用的功能&#xff0c;大家平时用这些黑魔法的时候有没有疑惑过一些疑问呢。 我们每天写的vue代码一般都是写在*.vue文件中&#xff0c;但是浏览器却只…

JavaSE面向对象进阶

static 介绍 static表示静态&#xff0c;是Java中的一个修饰符可以修饰成员方法、成员变量 被static修饰的成员变量&#xff0c;叫做静态变量被static修饰的成员方法&#xff0c;叫做静态方法 静态变量 特点&#xff1a;被该类所有对象共享 调用方式&#xff1a; 类名调用&am…

聚芯前行|美格智能亮相2024 ChinaJoy骁龙主题馆,展现数字娱乐的无限可能

7月26日&#xff0c;2024中国国际数码互动娱乐展览会&#xff08;ChinaJoy&#xff09;在上海新国际博览中心正式拉开帷幕。美格智能携手高通公司亮相骁龙主题馆&#xff0c;以5G-A毫米波MiFi解决方案及高算力AI模组&#xff0c;共同为广大玩家和粉丝打造了一个前沿技术赋能、充…

27-《木芙蓉》

木芙蓉 木芙蓉&#xff08;Hibiscus mutabilis Linn.&#xff09;又名芙蓉花、拒霜花、木莲、地芙蓉、华木&#xff0c;原产中国。其喜温暖、湿润环境&#xff0c;不耐寒&#xff0c;忌干旱&#xff0c;耐水湿。对土壤要求不高&#xff0c;瘠薄土地亦可生长。为锦葵科、木槿属落…

校园气象站

TH-XQ3校园气象站是一个用于测量和记录气象数据的设备&#xff0c;可以帮助学生和教师更好地了解校园的气候情况。以下是校园气象站的使用方法&#xff1a; 安装&#xff1a;校园气象站通常需要安装在一个开阔的区域&#xff0c;远离建筑物和树木等遮挡物。确保气象站稳固地安装…

中文网址导航模版HaoWa1.3.1/模版网站wordpress导航主题

HaoWa v1.3.1由挖主题开发的一款网址导航类主题。 HaoWA主题除主体导航列表外&#xff0c;对主题所需的小模块都进行了开放式的HTML编辑器形式的功能配置&#xff0c;同时预留出默认的代码结构&#xff0c;方便大家在现有的代码结构上进行功能调整。 同时加入了字体图标Font …

Bus Number

https://codeforces.com/problemset/problem/991/E 假想一下&#xff0c;如果我们知道m序列的长度是不是可以计算数量 这个好算&#xff0c;但是好像多了一点数&#xff0c;因为不能有前导零&#xff0c;所以我们要减去有前导零的部分 最后得到 那么我们只需要枚举数量即可&am…

【C#】 使用GDI+获取两个多边形区域相交、非相交区域

一、使用GDI获取两个多边形区域相交、非相交区域 在 C# 中使用 GDI&#xff08;Graphics Device Interface Plus&#xff09;处理图形时&#xff0c;你可以使用 System.Drawing 和 System.Drawing.Drawing2D 命名空间中的类来操作区域&#xff08;Region&#xff09;。下面是一…

Spark累加器(Accumulator)

1.累加器类型&#xff1a; 数值累加器&#xff1a;用于计算总和、计数等。布尔累加器&#xff1a;用于计算满足特定条件的次数。自定义累加器&#xff1a;允许定义复杂的聚合逻辑和数据结构。集合累加器&#xff1a;用于计算唯一元素的数量&#xff0c;处理去重操作。 在 Spar…

Study--Oracle-07-ASM常用操作(五)

一、向磁盘组添加磁盘 1、查看系统中可用的磁盘 set lines 150; col name for a35; col path for a35; select group_number,path, state, name, total_mb, free_mb from v$asm_disk; 2、磁盘组操作 创建磁盘组 create DISKGROUP DATADGV2 EXTERNAL REDUNDANCY DISK /dev/…

解决Qt3D程序场景中无法显示创建的立体图形?

有的新手在创建Qt3D程序时&#xff0c;因为不熟练&#xff0c;导致经常遇到无法显示3D图形的情况。 原因其实也简单&#xff0c;就是设置的摄像机的位置不对&#xff0c;或者压根没有设置摄像机。 // CameraQt3DRender::QCamera *cameraEntity view.camera();cameraEntity-&g…

Java二十三种设计模式-外观模式(9/23)

外观模式&#xff1a;简化复杂系统的统一接口 引言 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它为子系统中的一组接口提供一个统一的高层接口。外观模式定义了一个可以与复杂子系统交互的简化接口&#xff0c;使得子系统更加易于使用…

Android 10.0 Launcher3仿ios的folder文件夹widget功能实现二

1.前言 在10.0的系统ROM开发中,在进行一些系统Launcher3定制功能开发中,需要实现folder文件夹widget的功能,由于launcher3 默认不支持folder跨行显示,所以就需要借助自定义的widget小部件功能来实现相关功能,接下来分析实现相关功能 2.Launcher3仿ios的folder文件夹widge…

jQuery前端网页制作

1、Jquery的概述 1.1JavaScript库 JavaScript 高级程序设计(特别是对浏览器差异的复杂处理),通常很困难也很耗时。 为了应对这些调整,许多的 JavaScript (helper) 库应运而生。 这些 JavaScript 库常被称为 JavaScript 框架。 市面上一些广受欢迎的 JavaScript 框架:…

大厂linux面试题攻略五之数据库管理

一、数据库管理-MySQL语句 0.MySQL基本语句&#xff1a; 1.SQL语句-增 创建xxx用户&#xff1a; mysql>create user xxx % indentified by 123456; xxx表示用户名 %b表示该用户用来连接数据库的方式&#xff08;远程或本地连接&#xff09; indentified by 123456设置密码…

Reranker技术

文章目录 Reranker技术0. 什么是RAG1. 什么是Reranker&#xff1f;2. Reranker在RAG技术中的应用3.使用 Reranker 的优缺点4.总结参考&#xff1a;知乎 Reranker技术 0. 什么是RAG 基础 RAG 的操作流程大致如下&#xff1a;首先&#xff0c;你需要将文本切分成小段&#xff0…

centos7 docker空间不足

今天在使用docker安装镜像的时候&#xff0c;出现报错 查看原因&#xff0c;发现是分区空间不足导致的 所以考虑进行扩容 首先在vmware扩容并没有生效 因为只是扩展的虚拟空间&#xff0c;并不支持扩展分区大小&#xff0c;下面对分区进行扩容 参考&#xff1a; 分区扩容 主…

细说MCU的DAC改变输出信号频率的方法

目录 一、参考硬件 二、改变输出信号的频率 1.建立新工程 2.配置TIM3 三、代码修改 四、查看结果 一、参考硬件 本项目依赖的软件和硬件工程参考本文作者写的文章&#xff1a;细说MCU的DAC1和DAC2各自输出一通道模拟信号的方法-CSDN博客 https://wenchm.blog.csdn.net/a…