目录
6. 哈希表的模拟实现以及unorder_set和unorder_map的封装
1. 哈希概念
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即 O(log_2 N),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立 一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
插入元素时: 根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
搜索元素时:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功
该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)
哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。
用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快
2. 哈希冲突
在上面的例子中hash(4) = 4%10 = 4,hash(14) = 14%10 = 4,hash(4) == hash(14)。
不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。
把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”
3. 哈希函数
引起哈希冲突的一个原因可能是:哈希函数设计不够合理。
哈希函数设计原则:
1. 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
2. 哈希函数计算出来的地址能均匀分布在整个空间中
3. 哈希函数应该比较简单
常见哈希函数
1.. 直接定址法
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
优点:简单、均匀 缺点:需要事先知道关键字的分布情况
使用场景:适合查找比较小且连续的情况
2. 除留余数法
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数, 按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址
注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突
4. 哈希冲突解决
解决哈希冲突两种常见的方法是:闭散列和开散列
4.1 闭散列
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有 空位置,那么可以把key存放到冲突位置中的 “下一个” 空位置中去。
1. 线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
插入:
通过哈希函数获取待插入元素在哈希表中的位置
如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突, 使用线性探测找到下一个空位置,插入新元素
删除:
采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,14查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。
// 哈希表每个空间给个标记 EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
enum State{EMPTY, EXIST, DELETE};
扩容:散列表的载荷因子:e = 填入表中的元素个数 / 散列表的长度
e的值越大,发生哈希冲突的可能性越大
线性探测优点:实现非常简单
线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。
2. 二次探测:从发生冲突的位置开始,向后探测,但不是依次探测,而是每次向后找i^2(i = 1,2,3……)位置
研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任 何一个位置都不会被探查两次。
闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷。
闭散列的实现:
enum state
{
EMPTY,
EXITE,
DELETE
};
template<class K, class V>
struct Hash_Ndoe
{
//Hash_Ndoe(const pair<K, V>& kv)
// :_state(EMPTY),
// _kv(kv)
//{}
pair<K, V> _kv;
state _state = EMPTY;
};
template<class K, class V>
class Hash
{
typedef Hash_Ndoe<K, V> Node;
public:
bool Insert(const pair<K, V>& kv)
{
//判断空间是否足够
size_t _v_size = _v.size();
if (_v_size == 0)
{
_v.resize(10);
}
else if (_n * 10 / _v_size > 7)
{
size_t newsize = _v_size * 2;
Hash<K, V> newhash;
newhash._v.resize(newsize);
for (int i = 0; i < _v_size; ++i)
{
if (_v[i]._state == EXITE)
newhash.Insert(_v[i]._kv);
}
_v.swap(newhash._v);
}
_v_size = _v.size();
//找到对应位置
size_t index = kv.first % _v_size;
//线性探测
int x = 1;
while (_v[index]._state == EXITE)
{
if (_v[index]._kv.first == kv.first)
return false;
index = index + x;
index %= _v_size;
}
_v[index]._kv = kv;
_v[index]._state = EXITE;
_n++;
return true;
}
Node* Find(const K& key)
{
size_t _v_size = _v.size();
if (_v_size == 0)
return nullptr;
size_t index = key % _v_size;
size_t flag = index;
int x = 1;
while (_v[index]._state != EMPTY)
{
if (_v[index]._state == EXITE && _v[index]._kv.first == key)
return &_v[index];
index = index + x;
index %= _v_size;
if (flag == index)
break;
}
return nullptr;
}
bool Erase(const K& key)
{
Node* ret = Find(key);
if (ret == nullptr)
return false;
ret->_state = DELETE;
_n--;
return true;
}
private:
vector<Node> _v;
size_t _n = 0;
};
4.2 开散列
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。
3. 开散列增容 桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可 能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希 表进行增容,那该条件怎么确认呢?
开散列最好的情况是:每个哈希桶中刚好挂一个节点, 再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。e == 1
4.3 对于哈希表的补充
1. key的类型只能是能被%的类型,那其他类型怎么办
开一个模板接口,传仿函数,仿函数的返回值为整形
2. 如果一个类型没有重载==,或者类型的==并不是你想要的,怎么找到对应的值
再开一个模板接口,传仿函数,仿函数的返回值为整形
3. 除留余数法,最好模一个素数
template<class K, class V>
struct HashTableNode
{
pair<K, V> _kv;
HashTableNode<K, V>* _next;
HashTableNode(const pair<K, V>& kv)
:_kv(kv),
_next(nullptr)
{}
};
template<class T>
struct HashFcn
{
size_t operator()(const T& x)
{
return x;
}
};
template<>
struct HashFcn<string>
{
size_t operator()(const string& s)
{
size_t ret = 0;
for (auto ch : s)
{
ret += ch;
ret *= 31;
}
return ret;
}
};
template<class K, class V, class HashFcn = HashFcn<K>>
class HashTable
{
typedef HashTableNode<K, V> Node;
public:
HashTable() {}
HashTable(const HashTable<K, V>& hs)
:_n(hs._n)
{
size_t _v_size = hs._v.size();
_v.resize(_v_size);
for (size_t i = 0; i < _v_size; ++i)
{
Node* cur1 = hs._v[i];
Node* prev = nullptr;
while (cur1)
{
Node* newnode = new Node(cur1->_kv);
if (prev)
{
prev->_next = newnode;
}
else
{
_v[i] = newnode;
}
cur1 = cur1->_next;
prev = newnode;
}
}
}
~HashTable()
{
for (auto& cur : _v)
{
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
}
}
size_t GetNextSize(size_t num)//质数
{
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
};
size_t i = 0;
for (; i < __stl_num_primes; ++i)
{
if (__stl_prime_list[i] > num)
return __stl_prime_list[i];
}
return __stl_prime_list[i];
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
return false;
HashFcn hashfcn;
//扩容
size_t _v_size = _v.size();
if (_v_size == _n)
{
size_t newsize = GetNextSize(_v_size);
vector<Node*> newtable(newsize, nullptr);
for (auto& cur : _v)//cur类型 Node*&
{
while (cur)
{
Node* next = cur->_next;//记录当前节点的下一个
size_t newHashTablei = hashfcn(cur->_kv.first) % newsize;//计算新表的插入位置
//头插
cur->_next = newtable[newHashTablei];//连尾
newtable[newHashTablei] = cur;//改头
cur = next;//cur更新,连带更改cur的值****
}
}
_v.swap(newtable);
}
//头插
_v_size = _v.size();
size_t HashTablei = hashfcn(kv.first) % _v_size;
Node* newnode = new Node(kv);
newnode->_next = _v[HashTablei];
_v[HashTablei] = newnode;
_n++;
return true;
}
Node* Find(const K& key)
{
HashFcn hashfcn;
size_t _v_size = _v.size();
if (_v_size == 0 || _n == 0)
return nullptr;
size_t HashTablei = hashfcn(key) % _v_size;
Node* cur = _v[HashTablei];
while (cur)
{
if (cur->_kv.first == key)
return cur;
else
cur = cur->_next;
}
return nullptr;
}
bool Erase(const K& key)
{
HashFcn hashfcn;
size_t _v_size = _v.size();
if (_v_size == 0 || _n == 0)
return false;
size_t HashTablei = hashfcn(key) % _v_size;
Node* cur = _v[HashTablei];
Node* prev = nullptr;
while (cur)
{
if (cur->_kv.first == key)
{
if (prev == nullptr)
{
_v[HashTablei] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
_n--;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
private:
vector<Node*> _v;
size_t _n = 0;
};
5. 开散列与闭散列比较
应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上: 由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子a <= 0.7,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间。
6. 哈希表的模拟实现以及unorder_set和unorder_map的封装
hashtable.h
#pragma once
#include<assert.h>
template<class T>
struct HashFcn
{
size_t operator()(const T& x)
{
return x;
}
};
template<>
struct HashFcn<string>
{
size_t operator()(const string& s)
{
size_t ret = 0;
for (auto ch : s)
{
ret += ch;
ret *= 31;
}
return ret;
}
};
template<class T>
struct Equal_Key
{
bool operator()(const T& key1, const T& key2)
{
return key1 == key2;
}
};
namespace HashOpen
{
template<class V>
struct HashTableNode
{
typedef V value_type;
value_type _data;
HashTableNode<V>* _next;
HashTableNode(const value_type& data)
:_data(data),
_next(nullptr)
{}
};
template<class K, class V, class KeyOfValue, class HashFcn, class Pred>
class HashTable;
template<class K, class V, class Ptr, class Ref, class KeyOfValue, class HashFcn, class Pred>
struct __hash_iterator
{
typedef HashTableNode<V> Node;
typedef HashTable<K, V, KeyOfValue, HashFcn, Pred> Hash;
typedef __hash_iterator<K, V, Ptr, Ref, KeyOfValue, HashFcn, Pred> Self;
typedef __hash_iterator<K, V, V*, V&, KeyOfValue, HashFcn, Pred> iterator;
Node* _cur;
const Hash* _hs;
__hash_iterator(Node* cur, const Hash* hs)
:_cur(cur),
_hs(hs)
{}
__hash_iterator(const iterator& it)
:_cur(it._cur),
_hs(it._hs)
{}
Ref operator*()
{
return _cur->_data;
}
Ptr operator->()
{
return &operator*();
}
bool operator!=(const Self& t)
{
return _cur != t._cur;
}
Self& operator++()
{
KeyOfValue kov;
HashFcn hash;
if (_cur == nullptr)
{
assert(1);
return *this;
}
Node* next = _cur->_next;
if (next == nullptr)
{
size_t _v_size = _hs->_v.size();
size_t hashi = hash(kov(_cur->_data)) % _v_size;
++hashi;
_cur = nullptr;
for (size_t i = hashi; hashi < _v_size; ++hashi)
{
if (_hs->_v[hashi])
{
_cur = _hs->_v[hashi];
break;
}
}
}
else
{
_cur = next;
}
return *this;
}
};
template<class K, class V, class KeyOfValue, class HashFcn, class Pred>
class HashTable
{
template<class K, class V, class Ptr, class Ref, class KeyOfValue, class HashFcn, class Pred>
friend struct __hash_iterator;
typedef HashTableNode<V> Node;
typedef HashTable<K, V, KeyOfValue, HashFcn, Pred> Self;
typedef K key_type;
typedef V value_type;
public:
typedef __hash_iterator<K, V, V*, V&, KeyOfValue, HashFcn, Pred> iterator;
typedef __hash_iterator<K, V, const V*, const V&, KeyOfValue, HashFcn, Pred> const_iterator;
HashTable() {}
HashTable(const Self& hs)
:_n(hs._n)
{
size_t _v_size = hs._v.size();
_v.resize(_v_size);
for (size_t i = 0; i < _v_size; ++i)
{
Node* cur1 = hs._v[i];
Node* prev = nullptr;
while (cur1)
{
Node* newnode = new Node(cur1->_data);
if (prev)
{
prev->_next = newnode;
}
else
{
_v[i] = newnode;
}
cur1 = cur1->_next;
prev = newnode;
}
}
}
~HashTable()
{
for (auto& cur : _v)
{
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
}
}
iterator begin()
{
size_t _v_size = _v.size();
for (size_t i = 0; i < _v_size; ++i)
{
if (_v[i])
return iterator(_v[i], this);
}
return end();
}
iterator end()
{
return iterator(nullptr, this);
}
const_iterator begin() const
{
size_t _v_size = _v.size();
for (size_t i = 0; i < _v_size; ++i)
{
if (_v[i])
return const_iterator(_v[i], this);
}
return end();
}
const_iterator end() const
{
return const_iterator(nullptr, this);//const this
}
size_t GetNextSize(size_t num)//质数
{
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
};
size_t i = 0;
for (; i < __stl_num_primes; ++i)
{
if (__stl_prime_list[i] > num)
return __stl_prime_list[i];
}
return __stl_prime_list[i];
}
pair<iterator, bool> Insert(const value_type& data)
{
KeyOfValue kov;
iterator it = Find(kov(data));
if (it._cur)
return make_pair(it, false);
HashFcn hashfcn;
//扩容
size_t _v_size = _v.size();
if (_v_size == _n)
{
//size_t newsize = GetNextSize(_v_size);
size_t newsize = _v_size == 0 ? 10 : _v_size * 2;
vector<Node*> newtable(newsize, nullptr);
for (auto& cur : _v)//cur类型 Node*&
{
while (cur)
{
Node* next = cur->_next;//记录当前节点的下一个
size_t newHashTablei = hashfcn(kov(cur->_data)) % newsize;//计算新表的插入位置
//头插
cur->_next = newtable[newHashTablei];//连尾
newtable[newHashTablei] = cur;//改头
cur = next;//cur更新,连带更改cur的值****
}
}
_v.swap(newtable);
}
//头插
_v_size = _v.size();
size_t HashTablei = hashfcn(kov(data)) % _v_size;
Node* newnode = new Node(data);
newnode->_next = _v[HashTablei];
_v[HashTablei] = newnode;
_n++;
return make_pair(iterator(newnode, this), true);
}
iterator Find(const key_type& key)
{
KeyOfValue kov;
HashFcn hashfcn;
Pred pred;
size_t _v_size = _v.size();
if (_v_size == 0 || _n == 0)
return iterator(nullptr, this);
size_t HashTablei = hashfcn(key) % _v_size;
Node* cur = _v[HashTablei];
while (cur)
{
if (pred(kov(cur->_data), key))
return iterator(cur, this);
else
cur = cur->_next;
}
return iterator(nullptr, this);
}
bool Erase(const key_type& key)
{
HashFcn hashfcn;
KeyOfValue kov;
size_t _v_size = _v.size();
if (_v_size == 0 || _n == 0)
return false;
size_t HashTablei = hashfcn(key) % _v_size;
Node* cur = _v[HashTablei];
Node* prev = nullptr;
while (cur)
{
if (pred(kov(cur->_data), key))
{
if (prev == nullptr)
{
_v[HashTablei] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
_n--;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
private:
vector<Node*> _v;
size_t _n = 0;
};
}
unorder_set
#pragma once
#include"hashtable.h"
namespace kele
{
template<class K, class Hash = HashFcn<K>, class Pred = Equal_Key<K>>
class unorder_set
{
typedef K key_type;
typedef K value_type;
public:
struct _keyofvalue
{
const key_type& operator()(const value_type& value)
{
return value;
}
};
typedef typename HashOpen::HashTable<key_type, value_type, _keyofvalue, Hash, Pred>::const_iterator iterator;
typedef typename HashOpen::HashTable<key_type, value_type, _keyofvalue, Hash, Pred>::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();
}
pair<iterator, bool> insert(const value_type& value)
{
return ht.Insert(value);
}
iterator find(const key_type& key)
{
return ht.Find(key);
}
bool erase(const key_type& key)
{
return ht.Erase(key);
}
private:
HashOpen::HashTable<key_type, value_type, _keyofvalue, Hash, Pred> ht;
};
}
unorder_map
#pragma once
#include"hashtable.h"
namespace kele
{
template<class K, class V, class Hash = HashFcn<K>, class Pred = Equal_Key<K>>
class unorder_map
{
typedef K key_type;
typedef V data_type;
typedef pair<const K, V> value_type;
public:
struct _keyofvalue
{
const key_type& operator()(const value_type& value)
{
return value.first;
}
};
typedef typename HashOpen::HashTable<key_type, value_type, _keyofvalue, Hash, Pred>::iterator iterator;
typedef typename HashOpen::HashTable<key_type, value_type, _keyofvalue, Hash, Pred>::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();
}
pair<iterator, bool> insert(const value_type& value)
{
return ht.Insert(value);
}
iterator find(const key_type& key)
{
return ht.Find(key);
}
bool erase(const key_type& key)
{
return ht.Erase(key);
}
data_type& operator[](const key_type& key)
{
return (ht.Insert(make_pair(key, data_type())).first)->second;
}
private:
HashOpen::HashTable<key_type, value_type, _keyofvalue, Hash, Pred> ht;
};
}
test.cpp
#include<iostream>
#include<vector>
#include<string>
using namespace std;
#include"hashtable.h"
#include"unorderedset.h"
#include"unorderedmap.h"
void test_map1()
{
kele::unorder_map<int, int> m;
int arr[] = { 1, 2, 3, 4,5,55,15,46,66 ,1,22,2,2,55};
for (auto e : arr)
{
//m.insert(make_pair(e,e));
m[e]++;
}
//kele::unorder_map<int, int>::iterator it = m.begin();
//while (it != m.end())
//{
// cout << it->first << ":" << it->second << endl;
// ++it;
//}
for (auto it : m)
{
cout << it.first << ":" << it.second << endl;
}
}
class Date
{
friend struct HashFunc;
friend ostream& operator<<(ostream& out, const Date& x);
public:
Date(int year, int month, int day)
:_year(year),
_month(month),
_day(day)
{}
bool operator==(const Date x) const
{
return _year == x._year
&& _month == x._month
&& _day == x._day;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& x)
{
out << x._year << "/" << x._month << "/" << x._day;
return out;
}
struct HashFunc
{
size_t operator()(const Date* date)
{
size_t ret = 0;
ret = (ret + date->_year) * 31;
ret = (ret + date->_month) * 31;
ret = (ret + date->_day) * 31;
return ret;
}
};
struct Equal
{
bool operator()(const Date* date1, const Date* date2)
{
return *date1 == *date2;
}
};
void test_map2()
{
kele::unorder_map<Date*, int, HashFunc, Equal> m;
Date d1(2024, 3, 10);
Date d2(2024, 3, 11);
Date d3(2024, 3, 10);
Date d4(2024, 3, 12);
Date d5(2024, 3, 12);
Date* a[] = { &d1,&d2,&d3,&d4,&d5 };
for (auto e : a)
{
m[e]++;
}
for (auto e : m)
{
cout << *(e.first) << ":" << e.second << endl;
}
}
template<class K>
void print(const kele::unorder_set<int>& s)
{
for (auto& e : s)
{
//e++;
cout << e << " ";
}
}
void test_set1()
{
kele::unorder_set<int> s;
int arr[] = { 1, 2, 3, 4,5,55,15,46,66 ,1,22,2,2,55 };
for (auto e : arr)
{
s.insert(e);
}
print<int>(s);
}
template<class K, class V>
void print(const kele::unorder_map<int, int>& m)
{
for (auto& e : m)
{
//e.second++;
cout << e.first << ":" << e.second << " ";
}
}
int main()
{
//test_map1();
//test_map2();
test_set1();
return 0;
}
完