散列表(hashtable)是基于“开链的做法”。本质是一个vector和每个vector成员维护一个link list,同时vector只存放指针。需要注意针对这个list搜索只能一种线性操作。
hashtable 由桶子(buckets)(本质是vector)和 节点(node)(本质是list)组成。
其中该list 是单向,只有往后的迭代器。
hashtable 的数据结构
所以:buckets 就是桶子,node就是节点。 buckets只存放部分个(即只头部指针)元素数据。
其中node 结构定义:
迭代器结构
迭代器永远维系着与整个buckets vector 的关系,并记录当前所指的节点。
hashtable 创建、销毁的内存管理
默认也是居于alloc进行分配
1.创建节点。适配器分配内存,并调用构造函数初始化
2.删除节点:调用析构函数,并释放内存。
在hashtable构造函数时,对成员变量buckets的默认 初始化内存大小的依据是接近某数并大于某数的质数。后续增加元素,如果其总数大于现在的内存的大小,需要重新调整这个vector的大小,也就需要重新对所有节点重新归属在哪个bucket上。
插入元素与表格重整
如果插入元素后,其总数大于原整个vector的大小。就需要重整。重整意味着重新创建新的vector。重新对原来的节点进行归属哪个bucket上(依据于取模运算。value%vector的大小)
在这里使用的是bkt_num函数,就是取模运算。
1.插入的元素不允许重复
插入位置位于list的头部
2.插入的元素允许重复
重复的数据插入和其值相同元素的后面
整体删除元素(clear)
1.vector内容设置为空指针,不释放内存空间。
2.所有节点的内存释放掉。
复制(copy_from)
散列函数的构造方法
1.除留余数法
通常:若 散列表长为m,p为大于或等于表长(最好接近m)的最小质数。
2.直接定址法
3.数字分析法
4.平分取中法
5.折叠法
6.随机数法
处理散列冲突的办法:
1.链地址法(也叫分离链接法)
就是最上面说的“开链”思维,使用链表的方式连接落在同一地址的数据,缺点是如果同一地址上的链表数据很多,遍历链表的时间就长,整体的散列表性能就变差。所以在大于0.7的填充因子后,就需要重新调整散列表。调整的办法是:再散列
2.开放定址法
3. 再散列函数法
4.公共溢出区法
其他:
1.散列表可用于缓存数据(如web服务器上)
2.散列表适合用于防止重复。
自定义散列表
//自定义散列表
template <class T>
class HashTable
{
public:
HashTable(int size = 101);
~HashTable();
bool insert(const T &value);
bool remove(const T &value);
bool contains(const T &value);
void makeempty();
private:
vector<list<T>> m_vecList;
int m_nCurrentSize;
size_t hashfunc(const T &value);//散列函数
void rehash();
};
template <class T>
void HashTable<T>::makeempty()
{
for (auto & list : m_vecList)
{
list.clear();
}
m_vecList.clear();
m_vecList.shrink_to_fit();
}
HashTable<int> ht;
for (int i = 0; i < 20; i++)
{
ht.insert(i);
}
HashTable<std::string> ht2;
for (int i = 0; i < 20; i++)
{
std::string a = std::to_string(i);
ht2.insert(a);
}
//分离链接散列表的再散列
template <class T>
void HashTable<T>::rehash()
{
vector<list<T>> oldvec = m_vecList;
int isize = m_vecList.size();
m_vecList.resize(isize*2);
for (auto &list : m_vecList)
{
list.clear();
}
m_nCurrentSize = 0;
for (auto &oldlist : oldvec)
{
for (auto &x : oldlist)
{
insert(std::move(x));
}
}
}
template <class T>
size_t HashTable<T>::hashfunc(const T &value)
{
static hash<T> hf;
size_t size = hf(value);
size %= m_vecList.size();
return size;
}
//在c++11中,散列函数通过函数对象模板表示
//template<class T>
//class hash
//{
//public:
// size_t operator()(const T &key) {}
//};
//如string
//template<>
//class hash<std::string>
//{
//public:
// size_t operator()(const std::string &key)
// {
// size_t hashval = 0;
// for (char ch : key)
// {
// hashval = 37 * hashval + ch;
// }
// return hashval;
// }
//};
template <class T>
bool HashTable<T>::contains(const T &value)
{
auto &whichlist = m_vecList[hashfunc(value)];
return find(whichlist.begin(), whichlist.end(), value) != whichlist.end();
}
template <class T>
bool HashTable<T>::remove(const T &value)
{
auto &whichlist = m_vecList[hashfunc(value)];
auto iter = find(whichlist.begin(), whichlist.end(), value);
if (iter == whichlist.end())
{
return false;
}
whichlist.erase(iter);
m_nCurrentSize--;
return true;
}
template <class T>
bool HashTable<T>::insert(const T &value)
{
auto &whichlist = m_vecList[hashfunc(value)];
if (find(whichlist.begin(),whichlist.end(), value) != whichlist.end())
{
return false;
}
whichlist.push_back(value);
m_nCurrentSize++;
if (m_nCurrentSize >= m_vecList.size())
{
rehash();
}
return true;
}
template <class T>
HashTable<T>::~HashTable()
{
makeempty();
}
template <class T>
HashTable<T>::HashTable(int size /*= 101*/)
{
m_vecList.resize(size);
}