一、哈希查找
我们之前学过的,线性表、二叉搜索树、AVL树、红黑树和B树中,元素在存储结构中的位置与元素的关键码之间不存在直接的对应关系。在数据结构中搜索一个元素需要进行一系列的关键码比较。搜索的效率取决于搜索过程中比较的次数。
理想的搜索方法是可以不经过任何比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,使元素的存储位置与它的关键码之间建立一个确定的对应函数关系 Hash(),那么每个元素关键码与结构中的一个唯一的存储位置相对应:
Address = Hash(Key)
我们在插入时,根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。
在搜索时,对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。
该方式即散列方法(Hash Method),在散列方法中使用的转换函数叫着散列函数(Hash function),构造出来的结构叫散列表(Hash Table)。
二、哈希冲突(哈希碰撞)
对于两个数据元素的关键字Ki和Kj(i != j),有Ki != Kj(i != j),但HashFun(Ki) == HashFun(Kj),将该种现象称为哈希冲突或哈希碰撞。
把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”,由同义词引起的冲突称为同义词冲突。
三、
常见哈希函数
1、 直接定址法
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B。
优点:简单、均匀
缺点:需要事先知道关键字的分布情况适合查找比较小且连续的情况。
2、 除留余数法
设散列表中允许的地址数为m,取一个不大于m,但接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key % p p<=m,将关键码转换成哈希地址。
3、 平方取中法
3、 平方取中法
假设关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227作为散列地址;再比如关键字是4321,那么它的平方就是18671041,抽取中间的3位就可以是671或者710用作散列地址。
平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况。
4、折叠法
折叠法是将关键字从左到右分割成位数相等的几部分(注意:后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。比如:关键字是9876543210,散列表表长为三位,我们将它分成四组987|654|321|0|, 然后将它们叠加求和987+654+321+0=1962,再求后3位得到散列地址为962。有时可能这还不能够保证分布均匀,不妨从一段向另一端来回折叠后对齐相加。比如将987和321反转,再与654和0相加,编程789+654+123+0=1566,此时的散列地址为566。折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况。
5、随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为随机数函数,通常应用于关键字长度不等时采用此法。
6、数学分析法
设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布 均匀的若干位作为散列地址。
数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀的情况。
四、解决哈希冲突的方法
任何一种散列函数也不能避免产生冲突,因此选择好的解决冲突溢出的方法十分重要。为了减少冲突必须对散列表加以改造。
1. 闭散列(开放定址法)---从发生哈希冲突位置开始,找下一个空余位置
a. 线性探测,顾名思义,就是逐次往后找,如下图:
缺陷:当继续探测冲突的元素越来越多,查找的效率会越来越慢,
此时需要更大的空间来存放更多的冲突的数,这就需要二次探测;
此时需要更大的空间来存放更多的冲突的数,这就需要二次探测;
b. 二次探测,平方向后探测,如下图:
这里面的开更大的空间不是简单的二倍的关系,如果直接开比原来大两倍的空间,还是会出现和线性探测相同的问题,
因此需要素数表做除数,这样就会减少冲突数。
2、负载因子
定义:负载因子 a = 填入表中的个数/表的长度;
a是散列表装满程度的标志,a与“填入表中的元素成正比”,所以a越大,表明填入表中的元素越多,产生冲突的元素越多。
对于开放定址法负载因子特别重要,因该严格控制在0.7~0.8左右。
定义:负载因子 a = 填入表中的个数/表的长度;
a是散列表装满程度的标志,a与“填入表中的元素成正比”,所以a越大,表明填入表中的元素越多,产生冲突的元素越多。
对于开放定址法负载因子特别重要,因该严格控制在0.7~0.8左右。
我们知道闭散列方法当负载因子大于0.8时,哈希表的查找效率就会变得很低效,如何解决? 我们来了解一下开散列。
3、 开散列(拉链法--开链法)
开散列法又叫链地址法(开链法)。
开散列法首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点组成 一个向量,因此,向量的元素个数与可能的桶数一致。
开散列法首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点组成 一个向量,因此,向量的元素个数与可能的桶数一致。
4、素数表--使用素数做除数可以减少哈希冲突
const int _PrimeSize = 28;
static const unsigned long _PrimeList[_PrimeSize] =
{
53ul, 97ul, 13ul, 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
};
5、因为要考虑各种类型的数据,比如字符串,在计算地址时是无法求模的。
了解字符串哈希算法:
static size_t BKDRHash(const char * str)
{
unsigned int seed = 131; // 31 131 1313 13 131 1313 13
unsigned int hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return(hash & 0x7FFFFFFF);
}
五、闭散列的实现
因此我们在代码里用了一个伪函数,有两种使用方法,注意理解代码中伪函数和特化的区别,测试函数是不一样的。代码如下,
我用命名空间把代码括起来便于和开散列区分开来:
#include<string>
#include<iostream>
using namespace std;
#include<vector>
namespace CLOSE
{
typedef enum State{ Exist, Empty, Delete };
template<class K, class V>
struct HashNode
{
pair<K, V> _kv;
State _state;
};
//默认
template<class K>
struct __HashFunc
{
size_t operator()(const K& key)
{
return key;
}
};
struct __HashFuncString
{
static size_t BKDRHash(const char * str)
{
unsigned int seed = 131; // 31 131 1313 13 131 1313 13
unsigned int hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return(hash & 0x7FFFFFFF);
}
size_t operator()(const string& key)
{
return BKDRHash(key.c_str());
}
};
特化
//template<>
//struct __HashFunc<string>
//{
// static size_t BKDRHash(const char * str)
// {
// unsigned int seed = 131; // 31 131 1313 13 131 1313 13
// unsigned int hash = 0;
// while (*str)
// {
// hash = hash * seed + (*str++);
// }
// return(hash & 0x7FFFFFFF);
// }
// size_t operator()(const string& key)
// {
// return BKDRHash(key.c_str());
// }
//};
template<class K, class V, class HashFunc = __HashFunc<K>>
class HashTable
{
public:
HashTable()
:_count(0)
{
for (size_t i = 0; i <_tables.size(); i++)
_tables[i]._state = Empty;
}
bool Insert(pair<K, V>& kv)
{
//检查载荷因子,增容
_CheckCapacity();
size_t i = 1;
size_t index = _HashFunc(kv.first);
while (_tables[index]._state == Exist)
{
if (_tables[index]._kv.first == kv.first)
{
return false;
}
//线性探测
/*index++;
if (index == _tables.size())
{
index = 0;
}*/
//二次探测
index += i ^ 2;
index = index%_tables.size();
++i;
}
_tables[index]._kv = kv;
_tables[index]._state = Exist;
++_count;
return true;
}
//除留余数法
size_t _HashFunc(const K& key)
{
HashFunc hash;
return hash(key) % _tables.size();
}
HashNode<K, V>* Find(const K& key)
{
int Address = _HashFunc(key);
int startAdd = 0;
while (_tables[Address]._state != Empty)
{
if (_tables[Address]._kv.first == key&&_tables[Address]._state == Exist)
return &_tables[Address];
//线性探测
startAdd = Address; //记下开始寻找的起始位置,避免无限循环
Address++;
if (Address == _tables.size())
Address = 0; //避免越界,从头寻找
if (Address == startAdd) //寻找完每个数,未找到
return false;
}
return NULL;
}
bool Remove(const K& key)
{
HashNode<K, V>* Node = Find(key);
if (Node)
{
Node->_state = Delete; //改标记
_count--;
return true;
}
else
return false;
}
size_t Size()
{
return _count;
}
size_t Capacity()
{
return _tables.size();
}
protected:
//取得下一个素数
size_t GetNextPrime(size_t value)
{
//使用素数表(大量测试得到合适的数据)对齐做哈希表的容量,降低哈希冲突
const int _PrimeSize = 28;
static const unsigned long _PrimeList[_PrimeSize] =
{
53ul, 97ul, 13ul, 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
};
for (size_t i = 0; i < _PrimeSize; i++)
{
if (_PrimeList[i]>value)
{
return _PrimeList[i];
}
}
return _PrimeList[_PrimeSize - 1];
}
//增容
void _CheckCapacity()
{
if (_tables.size() == 0)
{
_tables.resize(GetNextPrime(0));
}
//载荷因子超过0.7增容
if (_count * 10 / _tables.size() >= 7)
{
size_t newSize = GetNextPrime(_tables.size()); //扩容
//建立新的哈希表
vector<HashNode<K,V>> _tables;
HashTable<K, V, HashFunc> newHT;
newHT._tables.resize(newSize);
for (size_t i = 0; i < _tables.size(); ++i)
{
if (_tables[i]._state == Exist)
{
newHT.Insert(_tables[i]._kv);
}
}
//交换新旧哈希表的tables
//swap(_tables,newHT._tables);不行,有临时变量,代价太大
_tables.swap(newHT._tables);
}
}
private:
vector<HashNode<K, V>> _tables;
size_t _count; //表里数据的个数
};
void TestHashTable()
{
HashTable<int, int> ht1;
ht1.Insert(make_pair(71, 0));
ht1.Insert(make_pair(72, 0));
ht1.Insert(make_pair(201, 0));
cout << ht1.Capacity() << endl;
cout << ht1.Size() << endl;
cout << ht1.Find(71)->_kv.first << endl;
ht1.Remove(71);
cout << ht1.Size() << endl;
//用特化的版本
//HashTable<string, string> dict;
HashTable<string, string, __HashFuncString> dict; //取模时,字符串不能操作
dict.Insert(make_pair(string("left"), string("左边")));
dict.Insert(make_pair(string("leave"), string("离开")));
dict.Insert(make_pair(string("right"), string("右边")));
cout << dict.Capacity() << endl;
cout << dict.Size()<< endl;
cout << dict.Find("leave")->_kv.first << endl;
dict.Remove("left");
cout << dict.Size() << endl;
}
}
测试:
六、开散列的实现
下面的代码是增加了迭代器的,因此在HashTable中声明了
HashTableIterator为友元类。
且将插入函数本返回值bool改为了pair<Iterator,bool>类型。
代码如下:
namespace BUCKET
{
template<class K, class V>
struct HashNode
{
HashNode<K, V> *_next;
pair<K, V> _kv;
HashNode(const pair<K, V>& kv)
:_kv(kv)
, _next(NULL)
{}
};
//默认
template<class K>
struct __HashFunc
{
size_t operator()(const K& key)
{
return key;
}
};
//struct __HashFuncString
//{
// static size_t BKDRHash(const char * str)
// {
// unsigned int seed = 131; // 31 131 1313 13 131 1313 13
// unsigned int hash = 0;
// while (*str)
// {
// hash = hash * seed + (*str++);
// }
// return(hash & 0x7FFFFFFF);
// }
// size_t operator()(const string& key)
// {
// return BKDRHash(key.c_str());
// }
//};
//特化
template<>
struct __HashFunc<string>
{
static size_t BKDRHash(const char * str)
{
unsigned int seed = 131; // 31 131 1313 13 131 1313 13
unsigned int hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return(hash & 0x7FFFFFFF);
}
size_t operator()(const string& key)
{
return BKDRHash(key.c_str());
}
};
//声明HashTable
template<class K, class V, class HashFunc>
class HashTable;
//迭代器
template<class K, class V, class HashFunc>
struct HashTableIterator
{
typedef HashNode<K, V> Node;
typedef HashTableIterator<K, V, HashFunc> Self;
Node *_pNode;
HashTable<K, V, HashFunc> *_ht;
HashTableIterator(Node *pNode, HashTable<K, V, HashFunc>* ht)
:_pNode(pNode)
, _ht(ht)
{}
pair<K, V>* operator*()
{
return _pNode->_kv;
}
pair<K, V>* operator->()
{
return &(_pNode->_kv);
}
Self& operator++()
{
//如果当前桶没走完,继续走
if (_pNode->_next)
{
_pNode = _pNode->_next;
}
//否则,找下一个不为空的桶
else
{
HashFunc hf;
//size_t index = ht.HashFunc();
size_t index = hf(_pNode->_kv.first) % _ht->_tables.size();
_pNode = NULL; //
for (size_t i = index + 1; i < _ht->_tables.size(); ++i)
{
if (_ht->_tables[i]) //桶不为空
{
_pNode = _ht->_tables[i];
break;
}
}
}
return *this;
}
/*Self& operator()--
{
//单向链表不需要--
}*/
bool operator!=(const Self& s)const
{
return _pNode != s._pNode;
}
};
template<class K, class V, class HashFunc = __HashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
friend struct HashTableIterator<K, V, HashFunc>;
public:
typedef HashTableIterator<K, V, HashFunc> Iterator;
HashTable()
:_count(0)
{}
~HashTable()
{
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
while (cur)
{
Node *next = cur->_next;
delete cur;
cur = next;
}
_tables[i] = NULL; //??
}
}
Iterator Begin()
{
for (size_t i = 0; i < _tables.size(); ++i)
{
if (_tables[i])
{
return Iterator(_tables[i], this);
}
}
return Iterator(NULL, this);
}
Iterator End()
{
return Iterator(NULL, this);
}
V& operator[](const K& key)
{
pair<Iterator, bool> ret = Insert(key, V());
return ret.first->second;
}
pair<Iterator,bool> Insert(const pair<K, V>& kv)
{
_CheckCapacity();
size_t index = _HashFunc(kv.first, _tables.size());
Node* cur = _tables[index];
while (cur)
{
if (cur->_kv.first == kv.first)
//return false;
return make_pair(Iterator(cur, this),false);
cur = cur->_next;
}
//头插
Node *tmp = new Node(kv);
tmp->_next = _tables[index];
_tables[index] = tmp;
++_count;
//return true;
return make_pair(Iterator(tmp, this), true);
}
Node* Find(const K& key)
{
size_t index = _HashFunc(key, _tables.size());
Node *cur = _tables[index];
while (cur)
{
if (cur->_kv.first == key)
return cur;
cur = cur->_next;
}
return NULL;
}
bool Remove(const K& key)
{
size_t index = _HashFunc(key, _tables.size());
Node *cur = _tables[index];
Node *prev = NULL;
while (cur)
{
if (cur->_kv.first == key)
{
if (prev == NULL)
{
//cur在第一个位置
_tables[index] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
//如果大概知道数据数量的多少
void Resize(size_t size)
{
_tables.resize(GetNextPrime(size));
}
protected:
//取得下一个素数
size_t GetNextPrime(size_t value)
{
//使用素数表(大量测试得到合适的数据)对齐做哈希表的容量,降低哈希冲突
const int _PrimeSize = 28;
static const unsigned long _PrimeList[_PrimeSize] =
{
53ul, 97ul, 13ul, 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
};
for (size_t i = 0; i < _PrimeSize; i++)
{
if (_PrimeList[i]>value)
{
return _PrimeList[i];
}
}
return _PrimeList[_PrimeSize - 1];
}
//除留余数
size_t _HashFunc(const K& key, size_t size)
{
HashFunc hf;
return hf(key) % size;
}
void _CheckCapacity()
{
if (_tables.size() == _count)
{
size_t newSize = GetNextPrime(_tables.size());
//建立新的哈希表,不能使用插入,会新建节点
vector<Node*> newTables;
newTables.resize(newSize);
for (size_t i = 0; i < _tables.size(); i++)
{
Node *cur = _tables[i];
while (cur)
{
Node *next = cur->_next;
size_t index = _HashFunc(cur->_kv.first, newSize);//计算在新表中的位置
cur->_next = newTables[index]; //头插
newTables[index] = cur;
cur = next;
}
_tables[i] = NULL; //将之前的哈希表置空
}
_tables.swap(newTables);
}
}
protected:
vector<Node*> _tables;
size_t _count;
};
void TestHashTable()
{
HashTable<int, int> ht;
for (size_t i = 0; i < 100; i++)
{
ht.Insert(make_pair(rand() % 1000, i));
}
HashTable<string, string> dict;
//HashTable<string, string, __HashFuncString> dict;
dict.Insert(make_pair(string("left1"), string("左边")));
dict.Insert(make_pair(string("leave1"), string("离开")));
dict.Insert(make_pair(string("right1"), string("右边")));
dict.Insert(make_pair(string("left2"), string("左边")));
dict.Insert(make_pair(string("leave2"), string("离开")));
dict.Insert(make_pair(string("right2"), string("右边")));
HashTable<string, string>::Iterator it = dict.Begin();
while (it != dict.End())
{
cout << it->first << ":" << it->second << endl;
++it;
}
cout << dict.Find("left1")->_kv.first << endl;
dict.Remove("left2");
if (dict.Find("left") == NULL)
cout << "没找到\n" << endl;
}
}
结果: