在源码中,这两个STL容器都是共用一个框架的所以,我们就用一个框架实现两个
容器,代码细节都在注释上
HashTable.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include <algorithm>
#include <utility>
//namespace txh
//{
// enum state
// {
// EMPTY,//空
// EXITS,//存在
// DELETE,//被删除
// };
// template<class K,class V>//元素类
// class HashData//先画一个存放数据的蓝图,声明和定义
// {
// public:
// std::pair<K,V> _kv;//哈希表,我们要根据key(也是模板的K)值去模一个值,得到哈希值存到哈希表的位置
// //V是数据类型
//
// //我们在查找的时候不知道用什么表示为空,没有一个合适的数,0吗,-1,不对,因为这些都有可能是某个数0,-1
// //所以不能用,我们用另一种方法,那就是在每个数据上存一个标志代表是否为空
//
// //前面定义了enum类型,类名是state,
// state _state = EMPTY;//默认都是空
// };
//
//
//
// //线性探测法
// template<class K,class V,class keyConv>
// class HashTable
// {
// public:
// typedef HashData<K, V> data;
// keyConv kc;
// HashTable()
// :_n(0)
// {
// _table.resize(11);
// }
//
// //那如果key值不是int而是string 类型呢,我们不可能去特意在模板里判断一个数是否是string类型
// //那么我们可以传一个模板,因为我们hash主要的是要把key值转换成hash值
// //我们用一个模板类去封装转换成hash值的方式
// bool insert(const std::pair<K,V>& kv)//插入元素,我们用pair类接受
// {
// //如果表内有相同的值的话,就不插入了
// if (find(kv.first))
// {
// return false;
// }
//
// if (_n * 10 / _table.size() > 7)//负载因子大于0.7我们的冲突就会变大,导致效率下降
// //在线性探测中,所以我们就需要扩容,扩多少倍呢,扩质数倍其实是很好的,java中只是扩2倍,没有按质数扩
// //按质数扩会降低哈希冲突率
// {
// HashTable<K, V,keyConv> newtable;
// newtable._table.resize(2 * _table.size());
//
// for (auto& e:_table)//为什么要重新插入,是因为我们扩容之后,下标映射变了,所以要重新插入
// {
// if(e._state == EXITS)
// {
// /* int hashi = e._kv.first % newtable._table.size();
// newtable._table[hashi]._kv = e._kv;
// newtable._table[hashi]._state = e._state;*/
// newtable.insert(e._kv);//复用写法
// }
// }
// _table.swap(newtable._table);//2023年的现代写法,新表代替了旧表
// }
//
//
// //线性探测
// size_t hashi = kc(kv.first) % _table.size(); //成员里不用capacity的原因是我们的vector的下标引用只能引用size内的值,capacity
// //肯定大于等于size,所以肯定不能去模除capacity
// size_t i = 0;
// while (_table[hashi]._state != EMPTY)//查看当前表的每个元素类是否有空,没空一直继续
// {
// //如果当前的hashi被占了,直接去下一个厕所看(就是相邻的下一个),所以有了i
// //i控制每一轮要看的厕所不是上一次看过的,所以i++
// ++hashi;
// hashi %= _table.size();//防止越界
// }
// _table[hashi]._kv = kv;
// _table[hashi]._state = EXITS;
// ++_n;
// return true;
// }
//
// data* find(const K& key)
// {
// size_t hashi = kc(key) % _table.size();
// size_t i = 0;
// while (_table[hashi]._state != EMPTY)//这是线性探测
// {
// if (_table[hashi]._kv.first == key
// && _table[hashi]._state == EXITS)
// {
// return &_table[hashi];
// }
// hashi++;
// hashi %= _table.size();//防止越界
// }
//
// return nullptr;
// }
//
// bool erase(const K& key)
// {
// data* ret = find(key);
// if (!ret)
// {
// return false;
// }
// else
// {
// ret->_state = DELETE;
// _n--;
// return true;
// }
// }
// private:
// std::vector<data> _table;//当前存放元素的表,
// size_t _n;//代表当前表内有多少个元素
// //负载因子是什么呢,就是总共插入的元素 除以 总元素 就是负载因子
// };
//}
//
template <typename T>
struct HashFunc
{
size_t operator()(const T& key)//键值接受
{
size_t hashi = key;
return hashi;
}
};//声明键值转换函数是模板类,因为模板类只是个图纸
template <>//特化之前一定要有一个基础类
struct HashFunc<std::string>
{
size_t operator()(const std::string& key)
{
size_t hashi = 0;
for (auto e : key)//关键值转hash
{
hashi *= 131;
hashi += e;
}
return hashi;
}
};
namespace HashBucket
{
//开散列,拉链法
template<class T>//改造后只有一个参数,我们自动去识别是一个参数还是两个参数
struct HashNode
{
HashNode(const T& data) :_data(data), next(nullptr) {}
T _data;
HashNode* next;
};
template<class K, class T, class keyCon, class KeyofT>
class HashTable;//在hashIterator类前面声明,编译器就会跟着这个声明去找到相应的模板
//容器都有迭代器,所以我们要封装一个迭代器,因为我们的迭代器会有重载*,->这种,所以我们直接传ptr这种吧
template<class K,class T,class Ref,class Ptr,class keyCon,class KeyofT>
class _HashIterator
{
public:
typedef HashNode<T> node;
typedef _HashIterator<K, T, T&, T*, keyCon, KeyofT> iterator;
typedef HashTable<K, T, keyCon, KeyofT> HT;
_HashIterator(node* node1,HT* ht) :_node(node1),_ht(ht){}
_HashIterator(const iterator& it):_node(it._node),_ht(it._ht){}
typedef _HashIterator<K,T,Ref,Ptr,keyCon,KeyofT> self;
self& operator++()
{
//先看当前桶里是否有元素,如果后续有元素,那就在桶里++,如果桶里没有,那么就去其他桶
//里找
node* cur = _node->next;
if (cur)
{
_node = _node->next;
//返回迭代器本身
}
else
{
//去遍历这个桶,找到后续第一个有元素的桶
keyCon kc;
//找到当前迭代器里的节点的桶的位置
size_t hashi = kc(_node->_data) % _ht->_table.size();
//遍历这个桶
++hashi;
while (hashi < _ht->_table.size())
{
if (_ht->_table[hashi])
{
_node = _ht->_table[hashi];
break;
}
hashi++;
}
if (hashi == _ht->_table.size())
{
_node = nullptr;
}
}
return *this;
}
self operator++(int)
{
//后置++
self ret = self(_node,_ht);
node* cur = _node;
if (cur)
{
_node = _node->next;
//返回迭代器本身
}
else
{
//去遍历这个桶,找到后续第一个有元素的桶
keyCon kc;
//找到当前迭代器里的节点的桶的位置
size_t hashi = kc(_node->_data) % _ht->_table.size();
//遍历这个桶
++hashi;
while (hashi < _ht->_table.size())
{
if (_ht->_table[hashi])
{
_node = _ht->_table[hashi];
break;
}
hashi++;
}
if (hashi == _ht->_table.size())
{
_node = nullptr;
}
}
return ret;//返回迭代器对象的时候,会进行拷贝构造,所以不用担心生命周期
}
bool operator!=(const self& s)
{
return s._node != _node;
}
Ref operator*()
{
return _node->_data;
}
Ptr& operator->()
{
return &_node->_data;
}
public:
node* _node;//有了迭代器,我们就可以直接用迭代器,迭代器++,就是这里的_node = _node->next等
//我们的++,--操作还会设计到遍历桶,我们的遍历桶只在HashTable里面设计了,所以我们需要有一个
//可以操作HashTable的动作,因为都是算类,我们可以设计一个指针进行操作,我们用友元
HT* _ht;
};
//第二个才是数,第一个只是我们的键值
template<class K, class T, class keyCon,class KeyofT>
class HashTable
{
public:
//友元声明
template<class K,class T,class Ref,class Ptr,class KeyCon, class KeyofT>//声明这个_hashIterator是个模板类,告诉编译器我要这个_HashIterator模板类
//是HashTable类的友元
friend class _HashIterator;
typedef _HashIterator<K,T,T&,T*,keyCon,KeyofT> iterator;
typedef _HashIterator<K, T,const T&,const T*,keyCon,KeyofT> const_iterator;
//我们通过传的模板参数来控制我们的迭代器是否是const迭代器
typedef HashNode<T> node;
keyCon hash;
KeyofT koft;
size_t _stl_next_prime(size_t size)
{
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473, 4294967291
};
//这里存放素数,为什么设素数为长度呢,因为在模素数的
//时候,造成的哈希冲突会竟可能的好
for (int i = 0;i < __stl_num_primes;i++)
{
if (__stl_prime_list[i] > size)
{
return __stl_prime_list[i];
}
}
}
HashTable() :_n(0)
{
_table.resize(_stl_next_prime(_table.size()));
}
~HashTable()
{
for (auto e : _table)
{
node* cur = e;
while (cur)//把在桶里的元素删除
{
node* next = cur->next;
delete cur;
cur = next;
}
e = nullptr;
}
}
iterator begin()
{
node* cur = nullptr;
//找到第一个有元素的桶
for (size_t hashi = 0;hashi < _table.size();hashi++)
{
cur = _table[hashi];
if (cur)
{
break;
}
}
return iterator(cur,this);//传值返回,生命周期就不用管,他会执行一次拷贝构造
//匿名构造函数
}
iterator end()
{
return iterator(nullptr, this);//传值返回,生命周期就不用管,他会执行一次拷贝构造
}
const_iterator begin() const
{
node* cur = nullptr;
for (size_t i = 0; i < _table.size(); ++i)
{
cur = _table[i];
if (cur)
{
break;
}
}
return const_iterator(cur, this);
}
const_iterator end() const{return const_iterator(nullptr, this);}
std::pair<iterator,bool> insert(const T& data)
{
iterator ret = find(koft(data));
if (ret != end())//有该点的话
{
return std::make_pair(ret, true);
}
//拉链法中,当负载因子等于1的时候,哈希的冲突就会变大,桶里的元素可能最多,所以负载因子为1就扩容最好
if (_n == _table.size())//负载因子等于1
{
/*HashTable<K, V, keyCon> newHash;*/
/*newHash._table.resize(_table.size() * 2);*/
//这种做法很费效率,因为每次插入要重新new一个节点,并释放,所以同样映射的节点就不需要重新new了
std::vector<node*> newtable;
/*newtable.resize(_table.size() * 2,nullptr);*/
//扩容
newtable.resize(_stl_next_prime(_table.size()));
//重新映射
for (auto& e : _table)
{
node* cur = e;
//头插
while (cur)
{
node* next = cur->next;
//哈希重新映射
size_t hashi = hash(koft(cur->_data)) % newtable.size();
cur->next = newtable[hashi];//将未插入之前的头插到cur的next
newtable[hashi] = cur;
cur = next;
}
e = nullptr;
}
//现代写法
_table.swap(newtable);//旧表换新表,只是表新表的内容搬到旧表中
//新表不用手动释放,生命周期只在当前作用域中,如果出了作用域会自动销毁(调用析构函数)
}
//这里的kc是实例化后的对象,所以可以kc(kv.first),但是不能用hash(kv.first),因为hash不是一个实例化的对象,需要把他实例化
//之后才能调用
size_t hashi = hash(koft(data)) % _table.size();
//头插
node* newnode = new node(data);
newnode->next = _table[hashi];
_table[hashi] = newnode;
++_n;
return std::make_pair(iterator(newnode,this),true);
}
iterator find(const K& key)
{
size_t hashi = hash(key) % _table.size();//根据关键值算出哈希值
node* cur = _table[hashi];
//在桶里寻找
while (cur)
{
if (koft(cur->_data) == key)
{
return iterator(cur,this);
}
cur = cur->next;
}
return iterator(nullptr,this);
}
bool erase(const K& key)
{
size_t hashi = hash(key) % _table.size();
node* cur = _table[hashi];//找到了桶
node* prev = nullptr;
while (cur)
{
if (cur->_kv.first == key)
{
//如果是头节点的话
if (cur->_kv == _table[hashi])
{
_table[hashi] = cur->next;
}
else//找到的这个key值不是头结点的话
{
prev->next = cur->next;
}
delete cur;
--_n;
return true;
}
else//继续找
{
prev = cur;
cur = cur->next;
}
}
return false;
}
private:
std::vector<node*> _table;//每个位置存放hashnode类,里面有指针,存放的是头结点
size_t _n;
};
}
//先改造哈希表
//int main()
//{
//
// /*txh::HashTable<int,int,> h1;*/
//
// //h1.insert(std::make_pair(4,1));
// //h1.insert(std::make_pair(4,1));
// //h1.insert(std::make_pair(4,1));
// //h1.insert(std::make_pair(3,1));
//
// //h1.insert(std::make_pair(7,1));
//
// /*txh::HashTable<std::string, int,HashFunc<std::string>> h1;
// h1.insert(std::make_pair("苹果", 1));
// h1.insert(std::make_pair("香蕉", 1));
// h1.insert(std::make_pair("梨", 1));
// h1.insert(std::make_pair("苹果", 1));
// txh::HashData<std::string, int>* ret = h1.find("苹果");
// ret->_kv.second++;
// h1.erase("苹果");*/
//
// HashBucket::HashTable<int, int, HashFunc<int>> h1;
//
// h1.insert(std::make_pair(3, 1));
// h1.insert(std::make_pair(4, 1));
// h1.insert(std::make_pair(3, 1));
// h1.insert(std::make_pair(3, 1));
// h1.insert(std::make_pair(12, 1));
// h1.insert(std::make_pair(12, 1));
// return 0;
//}
template<class T>
struct compare
{
bool operator()(const T& t1,const T& t2)
{
return t1 > t2;
}
};
unordered_map.h
#pragma once
#include "HashTable.h"
#include "unordered_map.h"
#include "unordered_set.h"
#include <iostream>
namespace txh
{
template<class K,class T,class keyCon = HashFunc<K>>
class unordered_map
{
public:
struct mapkeyoft
{
const K& operator()(const std::pair<K,T>& kv)
{
return kv.first;
}
};
typedef typename HashBucket::HashTable<K, std::pair<K, T>, keyCon, mapkeyoft>::iterator iterator;
typedef typename HashBucket::HashTable<K, std::pair<K, T>, keyCon, 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();
}
std::pair<iterator, bool> insert(const std::pair<K, T>& data)
{
return _ht.insert(data);
}
T& operator[](const K& key)
{
std::pair<iterator, bool> ret = insert(std::make_pair(key
, T));
return ret.first->second;
//加->就是得到了data,然后second
}
iterator find(const K& key)
{
return _ht.find(key);
}
bool erase(const K& key)
{
_ht.erase(key);
}
private:
HashBucket::HashTable<K, std::pair<K, T>, keyCon, mapkeyoft> _ht;
};
}
unordered_set.h
#pragma once
#include "HashTable.h"
#include <iostream>
#include <utility>
namespace txh
{
template<class K,class keyCon = HashFunc<K>>
class unordered_set
{
struct keyofset
{
const K& operator()(const K& data)
{
return data;
}
};
public:
typedef typename HashBucket::HashTable<K, K, keyCon, keyofset>::const_iterator iterator;
typedef typename HashBucket::HashTable<K, K, keyCon, keyofset>::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();
}
std::pair<iterator, bool> insert(const K& data)
{
return _ht.insert(data);
}
iterator find(const K& data)
{
return _ht.find(data);
}
bool erase(const K& data)
{
return _ht.erase(data);
}
private:
HashBucket::HashTable<K, K, keyCon, keyofset> _ht;
};
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "unordered_map.h"
#include "unordered_set.h"
#include "HashTable.h"
#include <iostream>
#include <algorithm>
#include <utility>
int main()
{
txh::unordered_set<int, HashFunc<int>> s1;
s1.insert(1);
return 0;
}