Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
哈希的优点:
哈希最大的问题就是缓解哈希碰撞
,即对不同的关键字可能得到同一散列地址,即key1≠key2,而f(key1)=f(key2),这种现象称为碰撞。
常用的哈希算法:
常用的解决哈希冲突的方法:
注意:
比较重要的一点,封装了unordered_map后,若想将K设置为const K,那么初始化节点的时候需要在初始化列表初始化,不能后面再赋值。
散列表的负载因子定义:负载因子 = 表中的有效元素/散列表的长度。
线性探测的就是在哈希冲突之后他就会在后面找一个空的位置放入。
缺点:
enum State
{
EMPTY,//空的
EXIST,//存在
DELETE//删除
};
//节点默认是空
template<class K,class V>
struct CloseHashNode
{
public:
CloseHashNode(const std::pair<K,V>& data = std::pair<K,V>(),State state = EMPTY)
: _data(data)
,_state(state)
{}
std::pair<K, V> _data;//节点的值
State _state;//节点的状态
};
CloseHash也就是闭散列,实际上就是开辟一块数组,数组的每个节点的状态设置成空,并且因为我们删除元素不方便,我们需要关注如下几点:
template<class K,class V,class Hash = MyHash<K,V>>
class CloseHash
{
typedef CloseHashNode<K, V> Node;
private:
vector<Node> _hashtable;//哈希表
size_t _size; // 有效元素,扩容要用
};
由于每次插入前需要检测负载因子以及表的空间是否足够,然后判断是否键值冗余,然后通过传来的data进行散列算法,然后模上表长得到固定的位置。
插入时要注意两种情况:
bool Insert(const std::pair<K, V>& data)
{
//先检测一下容量
CheckCapacity();
//然后找到固定的位置进行操作
size_t index = Hash()(data.first) % _hashtable.size();
//键值不冗余才能插入
while (_hashtable[index]._data.first != data.first && _hashtable[index]._state != EMPTY)
{
index++;
index %= _hashtable.size();
}
if (_hashtable[index]._state == EMPTY)
{
//可以正常插入
_hashtable[index] = Node(data, EXIST);
_size++;
return true;
}
else
{
return false;
}
}
找到了设置状态,没找到返回false.
bool Delete(const K& key)
{
size_t index = Hash()(key) % _hashtable.size();
while (!(_hashtable[index]._state != EMPTY && _hashtable[index]._data.first == key)
&& _hashtable[index]._state != EMPTY)
{
//找到才能删除
index++;
index %= _hashtable.size();
}
//找到了或者没找到
if (_hashtable[index]._state == EMPTY|| _hashtable[index]._state == DELETE)
{
return false;
}
else
{
_hashtable[index]._state = DELETE;
_size--;
}
return true;
}
查找失败就是查找的时候找到了EMPTY的节点,若到了EMPTY的格子之前没找到就说明是被删除了或者是没有插入过。
//找到了返回下标
//失败了返回 整形最大值
size_t Find(const K& key)
{
size_t index = Hash()(key) % _hashtable.size();
while (_hashtable[index]._state != EMPTY
&& _hashtable[index]._data.first != key)
{
index++;
index %= _hashtable.size();
}
if (_hashtable[index]._state == EMPTY)
{
return -1;//找不到
}
else
{
return index;
}
}
///
/// 1.线性探测,闭散列,线性探测
///
namespace ljh0
{
enum State
{
EMPTY,//空的
EXIST,//存在
DELETE//删除
};
//节点默认是空
template<class K, class V>
struct CloseHashNode
{
public:
CloseHashNode(const std::pair<K, V>& data = std::pair<K, V>(), State state = EMPTY)
: _data(data)
, _state(state)
{}
std::pair<K, V> _data;
State _state;
};
template<class K>
struct MyHash
{
size_t operator()(const K& key)
{
return key;
}
};
//采用的是线性探测,即往后依次寻找的方式
template<class K, class V, class Hash = MyHash<K>>
class CloseHash
{
typedef CloseHashNode<K, V> Node;
private:
vector<Node> _hashtable;
size_t _size; // 有效元素,扩容要用
public:
CloseHash(size_t capacity = SIZE)
{
//一开始先开一点空间,这个空间在闭散列这里没有什么讲究,只需要将负载因子控制在0.7左右
_hashtable.resize(capacity);
}
void CheckCapacity()
{
//需要将之前元素全部重新映射
if (_size * 10 >= _hashtable.size() * 7)
{
//扩容
size_t newCapacity = _hashtable.size() * 2;
CloseHash tmp(newCapacity);
for (size_t i = 0; i < _hashtable.size(); ++i)
{
if (_hashtable[i]._state == EXIST)
{
tmp.Insert(_hashtable[i]._data);
}
}
std::swap(tmp._hashtable, _hashtable);
}
}
bool Insert(const std::pair<K, V>& data)
{
//先检测一下容量
CheckCapacity();
//然后找到固定的位置进行操作
size_t index = Hash()(data.first) % _hashtable.size();
//键值不冗余才能插入
while (_hashtable[index]._data.first != data.first && _hashtable[index]._state != EMPTY)
{
index++;
index %= _hashtable.size();
}
if (_hashtable[index]._state == EMPTY)
{
//可以正常插入
_hashtable[index] = Node(data, EXIST);
_size++;
return true;
}
else
{
return false;
}
}
bool Delete(const K& key)
{
size_t index = Hash()(key) % _hashtable.size();
while (!(_hashtable[index]._state != EMPTY && _hashtable[index]._data.first == key)
&& _hashtable[index]._state != EMPTY)
{
//找到才能删除
index++;
index %= _hashtable.size();
}
//找到了或者没找到
if (_hashtable[index]._state == EMPTY|| _hashtable[index]._state == DELETE)
{
return false;
}
else
{
_hashtable[index]._state = DELETE;
_size--;
}
return true;
}
//找到了返回下标
//失败了返回 整形最大值
size_t Find(const K& key)
{
size_t index = Hash()(key) % _hashtable.size();
while (_hashtable[index]._state != EMPTY
&& _hashtable[index]._data.first != key)
{
index++;
index %= _hashtable.size();
}
if (_hashtable[index]._state == EMPTY)
{
return -1;//找不到
}
else
{
return index;
}
}
};
}
研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。
所以二次探测的效率其实上是能比线性探测高上不少,但是他的浪费需要比线性探测更多,达到了50%。
实际上就是每次往后走i*i (i = 1,2,3....)
,或者往前走i*i(i=1,2,3...)
,也可以左右反复横跳。
从实现上,他的插入和删除,查找只需要更改的地方实际上也很少。
bool Insert(const std::pair<K, V>& data)
{
//先检测一下容量
CheckCapacity();
//然后找到固定的位置进行操作
size_t index = Hash()(data.first) % _hashtable.size();
int i = 1;
while (_hashtable[index]._state != EMPTY)
{
index = ( index + i*i) % _hashtable.size();
i++;
}
if (_hashtable[index]._state == EMPTY)
{
//可以正常插入
_hashtable[index] = Node(data, EXIST);
_size++;
return true;
}
else
{
return false;
}
}
bool Delete(const K& key)
{
size_t index = Hash()(key) % _hashtable.size();
int i = 1;
while (!(_hashtable[index]._state != EMPTY && _hashtable[index]._data.first == key)
&& _hashtable[index]._state != EMPTY)
{
//找到才能删除
index += i*i;
i++;
index %= _hashtable.size();
}
//找到了或者没找到
if (_hashtable[index]._state == EMPTY)
{
return false;
}
else
{
_hashtable[index]._state = DELETE;
_size--;
}
return true;
}
//找到了返回下标
//失败了返回 整形最大值
size_t Find(const K& key)
{
size_t index = Hash()(key) %_hashtable.size();
int i = 1;
while (!(_hashtable[index]._state != EMPTY && _hashtable[index]._data.first == key)
&& _hashtable[index]._state != EMPTY)
{
index += i * i;
i++;
index %= _hashtable.size();
}
if (_hashtable[index]._state == EMPTY)
{
return -1;//找不到
}
else
{
return index;
}
}
闭散列结束了,剩下的就是开散列啦!!!
根据下图,我们可以大致知道T就是pair
注意Hash函数针对的是key值,因为我们也是通过key值去定位对应桶的位置,而Pred也是关于key值的比较函数,因为key有可能是自定义类型,这个时候比较函数就需要我们自己写,或者重载operator=。
开散列概念
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码,归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
这种方式是如今最常用的,也是unordered_set/unordered_map的底层容器。
比起闭散列,开散列的优势:
template<class T>
struct LinkList
{
LinkList(struct LinkList* next = nullptr,const T& data = T())
:_next(next)
,_data(data)
{}
struct LinkList* _next;//单链表的next指针
T _data;//他的一个元素
};
插入操作涉及到是否需要扩容,为防止键值冗余有key值的比较,而模板类型T可能是pair,可能是K。
注意,虽然模板参数没有什么KeyOfT,但是我们实际编写代码的时候需要用到,所以我们可以将unordered_map的模板参数最后一个添加KeyOfT,并且可以给定一个固定的方法,不暴露给上层的同时又可以供给HashTanble使用。
std::pair<iterator, bool> Insert(const T& data)
{
CheckCapacity();
//哈希采用除留余数法
size_t index = Hash()(KOfT()(data)) % _openhash.size();
//先便利一遍单链表查找是否存在
LinkList<T>* first = _openhash[index];
LinkList<T>* tmp = first;
while (tmp)
{
if (Pred()(KOfT()(tmp->_data), KOfT()(data)))
{
return std::make_pair(iterator(tmp, this), false);
}
tmp = tmp->_next;
}
LinkList<T>* newList = new LinkList<T>(first, data);
/* newList->_next = first;
newList->_data = data;*/
_openhash[index] = newList;
_size++;
return std::make_pair(iterator(newList, this), true);
}
查找函数涉及key值的比较,但是我们实际上不知道模板参数T是什么类型,所以也不能直接进行比较,所以我们是通过KOfT和Pred,Hash函数进行两个的比较。
iterator Find(const K& key)
{
size_t index = Hash()(key) % _openhash.size();
if (!_openhash[index])
{
//end就表示没找到
return end();
}
LinkList<T>* head = _openhash[index];
const K& k = KOfT()(head->_data);
///
/// 这里的Pred是进行key值的比较,也就是需要先从T获取key值,所以KOfT必不可少
///
while (head && !Pred()(k, key))
{
head = head->_next;
}
if (!head)
return end();
return iterator(head, this);
}
删除的时候要注意删除的位置可能是桶的头节点的位置
,这个时候需要特判一下,把头结点的位置做转化。
size_t Erase(const K& key)
{
size_t index = Hash()(key) % _openhash.size();
if (!_openhash[index])
return 0;
LinkList<T>* cur = _openhash[index];
LinkList<T>* pre = nullptr;
while (cur)
{
if (Pred()(key, KOfT()(cur->_data)))
{
if (pre)
{
//删掉的不是桶的第一个位置
pre->_next = cur->_next;
}
else
{
//删除的就是桶
_openhash[index] = cur->_next;
}
delete cur;
return 1;
}
else
{
pre = cur;
cur = cur->_next;
}
}
return 0;
}
注意:
以下是 stl3.0 当中的定义的选取每次扩容过后的数组长度。
size_t GetNextPrime(size_t prime)
{
const int PRIMECOUNT = 28;
static const size_t primeList[PRIMECOUNT] =
{
53ul, 97ul, 193ul, 389ul, 769ul,
1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
};
size_t i = 0;
for (; i < PRIMECOUNT; ++i)
{
if (primeList[i] > prime)
return primeList[i];
}
return primeList[i];
}
void CheckCapacity()
{
if (_openhash.size() <= _size)
{
//获取下一个数字。
size_t newCapacity = GetNextPrime(_openhash.size());
OpenHash tmp(newCapacity);
//需要将所有的东西重新设置
for (size_t i = 0; i < _openhash.size(); ++i)
{
if (_openhash[i])
{
struct LinkList<T>* cur = _openhash[i];
while (cur)
{
tmp.Insert(cur->_data);
cur = cur->_next;
}
}
}
std::swap(tmp._openhash, _openhash);
}
}
template<class K,class T,class Hash,class Pred>
class OpenHashIterator
{
typedef LinkList<T> Node;
typedef OpenHashIterator<K,T,Hash, Pred> Self;
private:
Node* _node;
OpenHash<K,T,Hash, Pred>* _oh;//需要有原先的对象,这里的模板参数待定
public:
OpenHashIterator(Node* node, OpenHash<K, T, Hash,Pred>* oh)
:_node(node)
,_oh(oh)
{}
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
return *this;
}
//如果不在则需要找到下一个桶
size_t capacity = _oh->_openhash.size();
size_t index = Hash()(_node->_data) % capacity;
int f_index = -1;
for (size_t i = index+1; i < capacity; ++i)
{
if (_oh->_openhash[i])
{
f_index = i;
break;
}
}
if (f_index != -1)
{
_node = _oh->_openhash[f_index];
}
else
{
_node = nullptr;
}
return *this;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
T* operator->()
{
return &_node->_data;
}
T& operator*()
{
return _node->_data;
}
};
其中我们可以定义哈希算法,特化字符串哈希算法,以及KOfT的实现
。
#pragma once
#include
#include"HashTable.h"
#include
using std::string;
namespace ljh
{
template<class K>
struct hash1
{
const K& operator()(const K& k)
{
return k;
}
};
template<>
struct hash1<string>
{
size_t operator()(const string& k)
{
char* c_str = (char*)k.c_str();
unsigned int hash = 0;
while (*c_str)
{
// equivalent to: hash = 65599*hash + (*str++);
hash = (*c_str++) + (hash << 6) + (hash << 16) - hash;
}
return (hash & 0x7FFFFFFF);
}
};
template<class K>
struct pred
{
bool operator()(const K& k1, const K& k2)
{
return k1 == k2;
}
};
template<class K,class V>
struct MyKOfT
{
const K& operator()(const std::pair<const K, V>& kv)
{
return kv.first;
}
};
template<class K,class V,class Hash = hash1<K>,class Pred = pred<K>>
class unordered_map
{
public:
//这个KOfT不对上公开接口
typedef typename OpenHash<K, std::pair<const K, V>, Hash,Pred,MyKOfT<K,V>>::iterator iterator;
private:
OpenHash<K, std::pair<const K, V>, Hash,Pred ,MyKOfT<K, V>> _umap;
public:
iterator begin()
{
return _umap.begin();
}
iterator end()
{
return _umap.end();
}
std::pair<iterator, bool> insert(const std::pair<const K,V>& kv)
{
return _umap.Insert(kv);
}
size_t erase(const K& key)
{
return _umap.Erase(key);
}
V& operator[](const K& key)
{
//insert(make_pair(key, V()) -- > pair
//(insert(make_pair(key, V())).first iterator
//(insert(make_pair(key, V())).first)->second value type(因为里面封装的是一个pair& data)
///
return (insert(std::make_pair(key, V())).first)->second;
}
iterator find(const K& key)
{
return _umap.Find(key);
}
};
}
#pragma once
#include
#include"HashTable.h"
#include
#include
using std::vector;
using std::string;
namespace ljh
{
template<class K>
struct myhash
{
size_t operator()(const K& key)
{
return key;
}
};
template<>
struct myhash<string>
{
size_t operator()(const string& key)
{
char* c_str = (char*)key.c_str();
unsigned int hash = 0;
while (*c_str)
{
// equivalent to: hash = 65599*hash + (*str++);
hash = (*c_str++) + (hash << 6) + (hash << 16) - hash;
}
return (hash & 0x7FFFFFFF);
}
};
template<class K>
struct Pred
{
bool operator()(const K& k1,const K& k2)
{
return k1 == k2;
}
};
template<class K,class Hash = myhash<K>,class Pred = Pred<K>>
class unordered_set
{
private:
struct MyKOfT
{
const K& operator()(const K& key)
{
return key;
}
};
OpenHash<K,const K, Hash, Pred,MyKOfT> _uset;
public:
typedef typename OpenHash<K,const K, Hash, Pred, MyKOfT>::iterator iterator;
public:
iterator begin()
{
return _uset.begin();
}
iterator end()
{
return _uset.end();
}
pair<iterator,bool> insert(const K& key)
{
return _uset.Insert(key);
}
iterator find(const K& key)
{
return _uset.Find(key);
}
};
}
gitee链接:
https://gitee.com/wuyi-ljh/test-43—testing/tree/master/MyHashTable
常见的字符串哈希算法