前言
上一节介绍了 hashtable 的构造等功能的实现,本节将会介绍 hashtable 最重要的增删查改功能(与基于红黑树的 set 和 map 相同,hashtable 实现的 unordered_set 与 unordered_map 也不允许修改键值,仅允许修改 pair 类型元素的实值)。
增删元素
与 RB-tree 相同,由于需要同时支持 multiset 与 set 等容器,因此 hashtable 内部要提供两套不同的插入与删除元素的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public: template <class ...Args> iterator emplace_multi(Args&&... args);
template <class ...Args> tinystl::pair<iterator, bool> emplace_unique(Args&&... args);
iterator insert_multi_noresize(const value_type& value); pair<iterator, bool> insert_unique_noresize(const value_type& value);
iterator insert_multi(const value_type& value) { rehash_if_need(1); return insert_multi_noresize(value); }
iterator insert_multi(value_type&& value) { return emplace_multi(tinystl::move(value)); }
pair<iterator, bool> insert_unique(const value_type& value) { rehash_if_need(1); return insert_unique_noresize(value); }
pair<iterator, bool> insert_unique(value_type&& value) { return emplace_unique(tinystl::move(value)); }
template <class InputIterator> void insert_multi(InputIterator first, InputIterator last) { copy_insert_multi(first, last, tinystl::iterator_category(first)); }
template <class InputIterator> void insert_unique(InputIterator first, InputIterator last) { copy_insert_unique(first, last, tinystl::iterator_category(first)); }
void erase(const_iterator pos); void erase(const_iterator first, const_iterator last);
size_type erase_multi(const key_type& key); size_type erase_unique(const key_type& key);
|
emplace
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| template <class T, class Hash, class KeyEqual> template <class ...Args> typename hashtable<T, Hash, KeyEqual>::iterator hashtable<T, Hash, KeyEqual>::emplace_multi(Args&&... args) { auto np = create_node(tinystl::forward<Args>(args)...); try { if (static_cast<float>(size_ + 1) > static_cast<float>(bucket_size_) * max_load_factor()) { rehash(size_ + 1); } } catch (...) { destroy_node(np); throw; } return insert_node_multi(np); }
template <class T, class Hash, class KeyEqual> template <class ...Args> tinystl::pair<typename hashtable<T, Hash, KeyEqual>::iterator, bool> hashtable<T, Hash, KeyEqual>::emplace_unique(Args&&... args) { auto np = create_node(tinystl::forward<Args>(args)...); try { if (static_cast<float>(size_ + 1) > static_cast<float>(bucket_size_) * max_load_factor()) { rehash(size_ + 1); } } catch (...) { destroy_node(np); throw; } return insert_node_unique(np); }
|
emplace
会使用传入的参数原地构造一个 node,并判断当插入这个节点后需不需要重新哈希一次,rehash
会在下一小节进行说明,emplace
最终会调用 insert_node
将创建的节点插入哈希表,下面来看 insert_node
的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| template <class T, class Hash, class KeyEqual> typename hashtable<T, Hash, KeyEqual>::iterator hashtable<T, Hash, KeyEqual>::insert_node_multi(node_ptr node) { const auto n = hash(value_traits::get_key(node->value)); auto cur = buckets_[n]; if (cur == nullptr) { buckets_[n] = node; ++size_; return iterator(node, this); } for (; cur->next; cur = cur->next) { if (is_equal(value_traits::get_key(cur->value), value_traits::get_key(node->value))) { node->next = cur->next; cur->next = node; ++size_; return iterator(node, this); } } node->next = buckets_[n]; buckets_[n] = node; ++size_; return iterator(node, this); }
|
insert_node_multi
会先计算元素键值的哈希值,作为存放 node 的 bucket 的索引。如果该 bucket 为空则直接放在头部位置,否则会遍历 bucket,若有键值相同的节点会放在一起,方便查找;否则会放在 bucket 头部。该函数会返回插入位置的迭代器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| template <class T, class Hash, class KeyEqual> pair<typename hashtable<T, Hash, KeyEqual>::iterator, bool> hashtable<T, Hash, KeyEqual>::insert_node_unique(node_ptr node) { const auto n = hash(value_traits::get_key(node->value)); auto cur = buckets_[n]; if (cur == nullptr) { buckets_[n] = node; ++size_; return tinystl::make_pair(iterator(node, this), true); } for (; cur; cur = cur->next) { if (is_equal(value_traits::get_key(cur->value), value_traits::get_key(node->value))) { return tinystl::make_pair(iterator(cur, this), false); } } node->next = buckets_[n]; buckets_[n] = node; ++size_; return tinystl::make_pair(iterator(node, this), true); }
|
insert_node_unique
的流程也是先计算哈希值,再遍历对应的 bucket;不同之处在于不允许有重复节点,因此如果找到了相同值的节点就直接返回,否则也是插在 bucket 的头部。
该函数会返回一个 pair<iterator, bool>
,其中第一个值是插入位置的迭代器,第二个值代表是否插入成功。
insert
insert
的逻辑与 emplace
类似,这里就不再赘述,实际上这里功能的分割是有些不合理的,但是无伤大雅。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| template <class T, class Hash, class KeyEqual> typename hashtable<T, Hash, KeyEqual>::iterator hashtable<T, Hash, KeyEqual>::insert_multi_noresize(const value_type& value) { const auto n = hash(value_traits::get_key(value)); auto first = buckets_[n]; auto tmp = create_node(value); for (auto cur = first; cur; cur = cur->next) { if (is_equal(value_traits::get_key(cur->value), value_traits::get_key(value))) { tmp->next = cur->next; cur->next = tmp; ++size_; return iterator(tmp, this); } } tmp->next = first; buckets_[n] = tmp; ++size_; return iterator(tmp, this); }
template <class T, class Hash, class KeyEqual> tinystl::pair<typename hashtable<T, Hash, KeyEqual>::iterator, bool> hashtable<T, Hash, KeyEqual>::insert_unique_noresize(const value_type& value) { const auto n = hash(value_traits::get_key(value)); auto first = buckets_[n]; for (auto cur = first; cur; cur = cur->next) { if (is_equal(value_traits::get_key(cur->value), value_traits::get_key(value))) { return tinystl::make_pair(iterator(cur, this), false); } } auto tmp = create_node(value); tmp->next = first; buckets_[n] = tmp; ++size_; return tinystl::make_pair(iterator(tmp, this), true); }
|
erase
erase
函数负责删除某个迭代器位置处或某个迭代器区间内的元素,是 erase_unique
与 erase_multi
的基础。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| template <class T, class Hash, class KeyEqual> void hashtable<T, Hash, KeyEqual>::erase(const_iterator pos) { auto p = pos.node; if (p) { const auto n = hash(value_traits::get_key(p->value)); auto cur = buckets_[n]; if (cur == p) { buckets_[n] = cur->next; destroy_node(cur); --size_; } else { auto next = cur->next; while (next) { if (next == p) { cur->next = next->next; destroy_node(next); --size_; break; } else { cur = next; next = cur->next; } } } } }
|
该函数的逻辑也很好理解,先计算 pos
迭代器对应的 node 中元素的哈希值,确定 bucket 的位置后就进行遍历,删除节点。当节点位于 bucket 头部时需要特殊处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| template <class T, class Hash, class KeyEqual> void hashtable<T, Hash, KeyEqual>::erase(const_iterator first, const_iterator last) { if (first.node == last.node) return; auto first_bucket = first.node ? hash(value_traits::get_key(first.node->value)) : bucket_size_; auto last_bucket = last.node ? hash(value_traits::get_key(last.node->value)) : bucket_size_; if (first_bucket == last_bucket) { erase_bucket(first_bucket, first.node, last.node); } else { erase_bucket(first_bucket, first.node, nullptr); for (size_type n = first_bucket + 1; n < last_bucket; ++n) { if (buckets_[n] != nullptr) erase_bucket(n, nullptr); } if (last_bucket != bucket_size_) { erase_bucket(last_bucket, last.node); } } }
template <class T, class Hash, class KeyEqual> void hashtable<T, Hash, KeyEqual>::erase_bucket(size_type n, node_ptr first, node_ptr last) { auto cur = buckets_[n]; if (cur == first) { erase_bucket(n, last); } else { auto next = cur->next; for (; next != first; cur = next, next = cur->next) {} while (next != last) { cur->next = next->next; destroy_node(next); next = cur->next; --size_; } } }
template <class T, class Hash, class KeyEqual> void hashtable<T, Hash, KeyEqual>::erase_bucket(size_type n, node_ptr last) { auto cur = buckets_[n]; while (cur != last) { auto next = cur->next; destroy_node(cur); cur = next; --size_; } buckets_[n] = last; }
|
区间删除的逻辑要复杂一些,因为要区分 first 与 last 在同一个 bucket 与不同 bucket 的情况,为此分别提供了两个不同的 erase_bucket
函数用于辅助。
不过博主始终没能理解 auto first_bucket = first.node ? hash(value_traits::get_key(first.node->value)): bucket_size_;
这句代码的作用…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| template <class T, class Hash, class KeyEqual> typename hashtable<T, Hash, KeyEqual>::size_type hashtable<T, Hash, KeyEqual>::erase_multi(const key_type& key) { auto p = equal_range_multi(key); if (p.first.node != nullptr) { erase(p.first, p.second); return tinystl::distance(p.first, p.second); } return 0; }
template <class T, class Hash, class KeyEqual> typename hashtable<T, Hash, KeyEqual>::size_type hashtable<T, Hash, KeyEqual>::erase_unique(const key_type& key) { const auto n = hash(key); auto first = buckets_[n]; if (first) { if (is_equal(value_traits::get_key(first->value), key)) { buckets_[n] = first->next; destroy_node(first); --size_; return 1; } else { auto next = first->next; while (next) { if (is_equal(value_traits::get_key(next->value), key)) { first->next = next->next; destroy_node(next); --size_; return 1; } first = next; next = first->next; } } } return 0; }
|
erase_multi
内部调用 erase(first, last)
进行元素的删除,但是 erase_unique
却几乎重复了一遍 erase(pos)
,因为要在不同的地方 return,逻辑略有不同。
查找元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| template <class T, class Hash, class KeyEqual> typename hashtable<T, Hash, KeyEqual>::iterator hashtable<T, Hash, KeyEqual>::find(const key_type& key) { const auto n = hash(key); node_ptr first = buckets_[n]; for (; first && !is_equal(value_traits::get_key(first->value), key); first = first->next) {} return iterator(first, this); }
template <class T, class Hash, class KeyEqual> typename hashtable<T, Hash, KeyEqual>::size_type hashtable<T, Hash, KeyEqual>::count(const key_type& key) const { const auto n = hash(key); size_type result = 0; for (node_ptr cur = buckets_[n]; cur; cur = cur->next) { if (is_equal(value_traits::get_key(cur->value), key)) ++result; } return result; }
|
查找指定元素分为 find()
与 count()
两个函数,find()
用于查找是否有该元素,返回一个迭代器,如果没有的话,迭代器内的 node 将会是 nullptr
;count()
用于统计有多少个该元素,返回一个 size_type
类型的元素个数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| template <class T, class Hash, class KeyEqual> tinystl::pair<typename hashtable<T, Hash, KeyEqual>::iterator, typename hashtable<T, Hash, KeyEqual>::iterator> hashtable<T, Hash, KeyEqual>::equal_range_multi(const key_type& key) { const auto n = hash(key); for (node_ptr first = buckets_[n]; first; first = first->next) { if (is_equal(value_traits::get_key(first->value), key)) { for (node_ptr cur = first->next; cur; cur = cur->next) { if (!is_equal(value_traits::get_key(cur->value), key)) { return tinystl::make_pair(iterator(first, this), iterator(cur, this)); } } for (size_type m = n + 1; m < bucket_size_; ++m) { if (buckets_[m]) { return tinystl::make_pair(iterator(first, this), iterator(buckets_[m], this)); } } return tinystl::make_pair(iterator(first, this), end()); } } return tinystl::make_pair(end(), end()); }
template <class T, class Hash, class KeyEqual> tinystl::pair<typename hashtable<T, Hash, KeyEqual>::iterator, typename hashtable<T, Hash, KeyEqual>::iterator> hashtable<T, Hash, KeyEqual>::equal_range_unique(const key_type& key) { const auto n = hash(key); for (node_ptr first = buckets_[n]; first; first = first->next) { if (is_equal(value_traits::get_key(first->value), key)) { if (first->next) { return tinystl::make_pair(iterator(first, this), iterator(first->next, this)); } for (auto m = n + 1; m < bucket_size_; ++m) { if (buckets_[m]) { return tinystl::make_pair(iterator(first, this), iterator(buckets_[m], this)); } } return tinystl::make_pair(iterator(first, this), end()); } } return tinystl::make_pair(end(), end()); }
|
equal_range_multi
与 equal_range_unique
查找与键值与 key 相等的区间并返回,如果直到当前 bucket 的最后一个节点依然相等,区间的末尾将会返回下一个 bucket 的头部(如果有),或 end()
,即一个空指针。
总结
本节简要分析了 hashtable 的增删查操作,关于哈希表的内容也差不多该结束了,下一节将会分析基于 hashtable 实现的 unordered_set
与 unordered_map
容器。