富阳网站建站公司哪家好淘宝购物
一、unordered系列容器
在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到logN,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到。在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。
unordered_xxx系列与map和set容器的用法上几乎没有任何区别
他们的区别就是
unordered_xxx系列都是哈希表作为底层的,而map和set是用红黑树作为底层的
unordered_xxx系列不排序,只去重
unordered_xxx系列是单项迭代器
二、unordered_set
如下就是unordered_set的文档
unordered_set是一种容器,它以无特定顺序的方式存储唯一的元素,并允许根据元素的值快速检索各个元素。
在unordered_set中,元素的值同时也是它的键,唯一标识该元素。键是不可变的,因此,unordered_set中的元素一旦放入容器后就不能被修改,不过可以插入和删除。
在内部,unordered_set中的元素不按任何特定顺序排序,而是根据它们的哈希值组织成桶,以便通过它们的值(平均具有恒定的平均时间复杂度)直接快速访问各个元素。
对于通过键访问单个元素,unordered_set容器比set容器更快,尽管对于通过子集范围迭代它们通常效率较低。
容器中的迭代器是单向迭代器。
这些接口其实大差不差
void test1()
{unordered_set<int> us;us.insert(7);us.insert(5);us.insert(4);us.insert(3);us.insert(1);us.insert(6);unordered_set<int>::iterator usit = us.begin();while (usit != us.end()){cout << *usit << endl;usit++;}
}
我们可以自己试着用一用
三、unordered_map
如下是unordered_map的文档
unordered_map 是关联容器,用于存储由键值和映射值组合形成的元素,并允许根据各个元素的键快速检索各个元素。
在 unordered_map 中,键值通常用于唯一标识元素,而映射值是具有与此键关联的内容的对象。键和映射值的类型可能不同。
在内部,unordered_map中的元素不是根据其键值或映射值按任何特定顺序排序的,而是根据其哈希值组织到桶中,以允许直接通过其键值快速访问各个元素(平均平均时间复杂度保持不变)。
unordered_map 容器通过键访问单个元素的速度比 map 容器更快,尽管它们通常对其元素子集进行范围迭代的效率较低。
unordered_map实现直接访问运算符 (operator[]),它允许使用其键值作为参数直接访问映射值。
容器中的迭代器至少是单向迭代器。
我们可以试着用一用
void test2()
{unordered_map<string, string> dict;dict["insert"] = "插入";dict["sort"] = "排序";dict["delete"] = "删除";dict["string"] = "字符串";dict["insert"] = "xxxxx";dict.insert(make_pair("iterator", "迭代器"));unordered_map<string, string>::iterator umit = dict.begin();while (umit != dict.end()){cout << umit->first << ":" << umit->second << endl;umit++;}cout << endl;
}
四、unordered_set与set的比较
如下所示,我们采用如下代码进行比较
void test3()
{const size_t N = 100000;unordered_set<int> us;set<int> s;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; ++i){v.push_back(rand());//v.push_back(rand()+rand()); //减少重复数据//v.push_back(i); //有序的时候}size_t begin1 = clock();for (auto e : v){s.insert(e);}size_t end1 = clock();cout << "set insert:" << end1 - begin1 << endl;size_t begin2 = clock();for (auto e : v){us.insert(e);}size_t end2 = clock();cout << "unordered_set insert:" << end2 - begin2 << endl;size_t begin3 = clock();for (auto e : v){s.find(e);}size_t end3 = clock();cout << "set find:" << end3 - begin3 << endl;size_t begin4 = clock();for (auto e : v){us.find(e);}size_t end4 = clock();cout << "unordered_set find:" << end4 - begin4 << endl << endl;cout <<"插入数据个数:"<< s.size() << endl;cout <<"插入数据个数:" << us.size() << endl << endl;;size_t begin5 = clock();for (auto e : v){s.erase(e);}size_t end5 = clock();cout << "set erase:" << end5 - begin5 << endl;size_t begin6 = clock();for (auto e : v){us.erase(e);}size_t end6 = clock();cout << "unordered_set erase:" << end6 - begin6 << endl << endl;
}
可以看到,在无序的数据的时候,unordered_set更占优势一些。
但是我们会发现有很多的重复数据,于是我们可以对随机值+随机值以此减少重复数据
可以看到,还是unordered_set占据优势
在有序的数据时,此时set占据优势
因此,如果数据是无序的,unordered_set更优,如果是有序的,使用set更好
五、哈希与各种查找的比较
1.直接查找
就是我们最常见的暴力查找,他的时间复杂度是O(N)
2.二分
不过我们可以对其进行一定程度的优化,即先排序,这样的画他的时间复杂度就变为了logN,但是增删还是不方便,而且排序也需要时间。
3.平衡树
再后来就是使用红黑树,他的效率都是很优秀的,增删查改都是logN
4.哈希
即存储的值和存储位置建立出一个对应关系,这样的话时间复杂度直接变为了O(1),哈希我们也称作散列,哈希的方式有点类似于计数排序
5.STL中的哈希
STL库中,set类和map类都是红黑树作为底层实现的,与之类似,unordered系列的unordered_set类和unordered_map类,都是通过哈希表作为底层来实现的。
六、哈希表的修改
下面是我们之前写的哈希表
namespace hash_bucket
{template<class K,class V>struct HashNode{pair<K, V> _kv;HashNode<K, V>* _next = nullptr;HashNode(const pair<K, V>& kv):_kv(kv){}};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 hashi = 0;for (auto ch : str){hashi = hashi * 131 + ch;}return hashi;}};template<class K, class V, class HashFunc = DefaultHashFunc<K>>class HashTable{typedef HashNode<K,V> Node;public:HashTable():_n(0){_table.resize(10, nullptr);}bool Insert(const pair<K, V>& kv){if (Find(kv.first)){return false;}HashFunc hf;if (_n == _table.size()){size_t newSize = _table.size() * 2;vector<Node*> newTable(newSize, nullptr);for (int i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;size_t hashi = hf(cur->_kv.first) % newTable.size();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;}void Print(){for (int i = 0; i < _table.size(); i++){Node* cur = _table[i];printf("[%d]->", i);while (cur){cout << cur->_kv.first << ":" << cur->_kv.second << "->";cur = cur->_next;}cout << "NULL" << endl;}}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){prev->_next = cur->_next;}else{_table[hashi] = cur->_next;}delete cur;cur = nullptr;_n--;return true;}else{prev = cur;cur = cur->_next;}}return false;}~HashTable(){for (int i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}}private:vector<Node*> _table;size_t _n;};
}
现在我们需要为了把它改装成unordered系列容器
1.结点
template<class T>
struct HashNode
{HashNode<T>* _next;T _data;HashNode(const T& data): _next(nullptr), _data(data){}
};
对于哈希表的修改与之前我们用红黑树去封装map和set类似。
首先是将结点都改为T类型的,这个T类型对于set而言是K,对于map而言是pair<K,V>
2.迭代器
改造后的哈希表,最重要的功能之一就是支持单向迭代器
template<class K, class T, class KeyOfT, class Hash>
class HashTable;//前置声明template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
struct HashIterator
{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> Ht;typedef HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;typedef HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;Node* _node;const Ht* _ht;HashIterator(Node* node, const Ht* ht): _node(node), _ht(ht){}HashIterator(const Iterator& it): _node(it._node), _ht(it._ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &(operator*());}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
};
- 这里增加_ht成员变量,这样当一条单链表走到空,可以走到下一个哈希桶的位置,所以需要哈希表的地址
- 这里存在相互引用的问题,所以前置声明哈希表
- const修饰_ht,使const迭代器能够被构造
- 迭代器的拷贝构造函数有两个用途:
- 以普通迭代器拷贝出普通迭代器(普通迭代器调用时)
- 以普通迭代器拷贝出const迭代器(const迭代器调用时)
3.operator++
Self& operator++()
{if (_node->_next){_node = _node->_next;}else{int flag = 0;size_t hashi = Hash()(KeyOfT()(_node->_data)) % _ht->_tables.size();for (size_t i = hashi + 1; i < _ht->_tables.size(); ++i){if (_ht->_tables[i]){_node = _ht->_tables[i];flag = 1;break;}}if (!flag){_node = nullptr;}}return *this;
}Self operator++(int)
{Self tmp = *this;++*this;return tmp;
}
- 前置++的思路:
- 下一个结点不为空,则跳到下一位
- 下一个结点为空,则先取模算出哈希地址,再往后探测不为空的哈希桶
- 后置++:复用前置++,返回临时对象
4.本体
1.成员变量和默认成员函数
template<class K, class T, class KeyOfT, class Hash>
class HashTable
{template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>friend struct HashIterator;
protected:typedef HashNode<T> Node;
public:HashTable(){_tables.resize(10);}~HashTable(){for (auto& cur : _tables){while (cur){Node* del = cur;cur = cur->_next;delete del;}}}
protected:vector<Node*> _tables;size_t _n = 0;//有效数据个数
};
- 将迭代器声明为友元,使迭代器内部可操作_tables
- 第三个模板参数为KeyOfT(仿函数),用于获取不同数据T的键值key来进行比较
- 第四个模板参数为Hash(仿函数),用于将不同类型key转换为整型来进行取模
2.begin和end
typedef HashIterator<K, T, T&, T*, KeyOfT, Hash> iterator;
typedef HashIterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;iterator begin()
{for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]){return iterator(_tables[i], this);}}return iterator(nullptr, this);
}const_iterator begin() const
{for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]){return const_iterator(_tables[i], this);}}return const_iterator(nullptr, this);
}iterator end()
{return iterator(nullptr, this);
}const_iterator end() const
{return const_iterator(nullptr, this);
}
- begin返回最开始不为空的哈希桶的迭代器,end返回空迭代器
- 构造迭代器需要传入哈希表本身的地址,这里直接传this指针即可
3.Find
iterator Find(const K& key)
{size_t hashi = Hash()(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (KeyOfT()(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return iterator(nullptr, this);
}
- 返回迭代器
- Hash转整型,KeyOfT获取键值
4.Insert
pair<iterator, bool> Insert(const T& data)
{KeyOfT kot;iterator it = Find(kot(data));if (it._node)//保持key唯一{return make_pair(it, false);}Hash hash;if (_n == _tables.size())//负载因子为1时,扩容{size_t newsize = _tables.size() * 2;vector<Node*> newtables(newsize);for (auto& cur : _tables){while (cur){Node* next = cur->_next;//将旧表结点重新映射到新表上size_t hashi = hash(kot(cur->_data)) % newsize;cur->_next = newtables[hashi];newtables[hashi] = cur;//跳回旧表的下一结点cur = next;}}_tables.swap(newtables);}size_t hashi = hash(kot(data)) % _tables.size();Node* newnode = new Node(data);//头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(iterator(newnode, this), true);
}
- 返回pair,第一个参数为迭代器,第二个参数为布尔值(记录是否插入成功)
5.Erase
bool Erase(const K& key)
{size_t hashi = Hash()(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (KeyOfT()(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;
}
5.unordered_set
1 成员变量与仿函数
template<class K, class Hash = HashFunc<K>>
class unordered_set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public:
protected:HashTable<K, K, SetKeyOfT, Hash> _ht;
};
- unordered_set类仿函数,直接返回参数key
- 成员变量的第二个模板参数为K,第三个模板参数为SetKeyOfT
- 模板Hash可以根据特定需要而传手动实现的哈希化函数
2 begin和end
typedef typename HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator;
typedef typename HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator;iterator begin()
{return _ht.begin();
}const_iterator begin() const
{return _ht.begin();
}iterator end()
{return _ht.end();
}const_iterator end() const
{return _ht.end();
}
3 find
iterator find(const K& key)
{return _ht.Find(key);
}
4 insert
pair<iterator, bool> insert(const K& key)
{return _ht.Insert(key);
}
5 erase
bool erase(const K& key)
{return _ht.Erase(key);
}
6.unordered_map
1 成员变量与仿函数
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};
public:
protected:HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};
- unordered_map类仿函数,返回参数pair的first
- 成员变量的第二个模板参数为pair,第三个模板参数为MapKeyOfT
- 模板Hash可以根据特定需要而传手动实现的哈希化函数
2 begin和end
typedef typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;
typedef typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::const_iterator const_iterator;iterator begin()
{return _ht.begin();
}const_iterator begin() const
{return _ht.begin();
}iterator end()
{return _ht.end();
}const_iterator end() const
{return _ht.end();
}
3 find
iterator find(const K& key)
{return _ht.Find(key);
}
4 insert
pair<iterator, bool> insert(const pair<const K, V>& kv)
{return _ht.Insert(kv);
}
5 erase
bool erase(const K& key)
{return _ht.Erase(key);
}
6 operator[ ]
V& operator[](const K& key)
{pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;
}