哈希:用来进行高效查找的一种数据结构
首先,我们查找的方式有:
(1)顺序查找,它的时间复杂度是O(n)
(2)二分查找(有序),它的时间复杂度是O(log2N)
(3)利用二叉平衡搜索树(AVL、红黑树):时间复杂度是O(log2N)
(4)哈希,时间复杂度是O(1)
前三种方法元素之间都要进行比较,因此时间复杂度降不下来,而哈希元素之间不需要比较(最差情况下只需少许比较即可找到),因此时间复杂度小。
那么哈希的原理就是通过某种方式,将元素与其在空间中的位置建立一一对应的关系,例如:
给一个容量为capacity的空间,按照func(x)=x%capacity的方法来存储元素,比如存储21、67、90、33、5,func(21)=21%10=1,也就是21放在1号位置;func(67)=67%10=7,放在7号位置,func(90)=0,放在0号位置,func(33)=3,放在3号位置,func(5)=5,放在5号位置,这样就把元素放好了,也就是一个表格;
然后进行查找,第一步就是通过func(x)找元素在表格中的存储位置,然后验证是否为所找的元素即可;
这种思想就是哈希的思想,也可以称作为散列,func(x)称为哈希函数,建立的表格就是哈希表。
但是这种方法有一个缺陷,例如向这个哈希表中放入一个11,func(11)=1,应该将11放在1号位置,而这时1号位置已经放了一个元素,这时如果再向里面放元素就发生了覆盖(冲突),也就是不同的元素计算出相同的哈希地址,这种情况称为哈希冲突。
哈希冲突的解决方式:
(1)哈希函数可能会导致哈希冲突:可以重新设计哈希函数:
注意:哈希地址必须在哈希表格的范围内,产生的哈希地址尽可能的均匀分布,哈希函数尽可能简单;但是一个哈希函数无论设计的多精妙,都无法完全解决哈希冲突,只能将发生哈希冲突的概率降低。
(2)存放的元素
常见的哈希函数:
(1)直接定值法:Hash(key)=A*key+B,它的优点是简单和均匀,缺点是事先要知道关键字的分布,适用于查找比较小连续的情况,例如在字符串中找第一个只出现一次的字符
class Solution {
public:
int firstUniqChar(string s) {
int count[256]={0};//一个字符有256种状态
//统计每个字符出现的次数
for(int i=0;i
(2)除留余数法,也就是上述我们所说的func(x)=x%capacity的方法,也就是Hash(key)=key%p(如果p是素数,出现哈希冲突的概率较低)
(3)平方取中法(了解)例如关键字是1234,对他取平方得到1522756,取227作为他的关键字
(4)折叠法(5)随机数法(6)数学分析法
#pragma once
#include
using namespace std;
#include
//假设哈希表格中的元素是唯一的
enum State
{
EMPTY, EXIST, DELETE
};
template
struct Elem
{
T _value;//元素值域
State _state;//状态
};
template
class HashTable
{
public:
HashTable(size_t capacity = 10)
:_ht(capacity)
, _size(0)
{
for (auto& e : _ht)
{
e._state = EMPTY;//将表格的初始状态初始化为空
}
}
bool Insert(const T& val)
{
//检测是否需要扩容
CheckCapacity();
//通过哈希函数计算元素在哈希表中的存储位置
size_t HashAddr = HashFunc(val);
//检测该位置是否可以插入元素
//发生哈希冲突,使用线性探测来解决
while (_ht[HashAddr]._state != EMPTY)
{
if (EXIST == _ht[HashAddr]._state && val == _ht[HashAddr]._value)
{
//就不用插入了,冲突
return false;
}
//使用线性探测继续往后找,直到找到空位
++HashAddr;
if (HashAddr == _ht.capacity())
HashAddr = 0;//如果找到最后一个还没有找到空位,从头开始
}
//肯定不会让哈希表中的元素放的太多,因为发生冲突的概率会提高,这样哈希表查找的效率就会降低
//所以不用考虑造成死循环的情况
//找到空的位置,进行插入
_ht[HashAddr]._value = val;
_ht[HashAddr]._state = EXIST;
++_size;
return true;
}
int Find(const T& val)
{
size_t HashAddr = HashFunc(val);//计算哈希地址
while (_ht[HashAddr]._state != EMPTY)//这个位置可能有元素
{
if (_ht[HashAddr]._state == EXIST && _ht[HashAddr]._value == val)
{
return HashAddr;
}
//如果这个位置是删除或者不等于要找的值,就哈希冲突,线性探测
HashAddr++;
if (HashAddr == _ht.capacity())
HashAddr = 0;
}
return -1;//没有这个元素
}
bool Erase(const T& val)
{
int index = Find(val);//在哈希表中找这个元素的位置
if (index != -1)//找到了
{
_ht[index]._state = DELETE;
_size--;
return true;
}
return false;
}
void Swap(HashTable& ht)
{
_ht.swap(ht._ht);//先交换内容
swap(_size, ht._size);
}
private:
size_t HashFunc(const T& val)
{
return val%_ht.capacity();
}
void CheckCapacity()
{
//有效元素与容量的比率称为负载因子,因为_size/_ht.capacity()永远是0,因为都是整形,所以给_size乘10/容量>7即可
if (_size*10 / _ht.capacity() >= 7)//需要扩容
{
//无法使用原来的哪种方法扩容,因为现在哈希函数是val%容量,扩容使容量发生改变,哈希函数也就会变化,
//如果采用原来的方法将元素进行搬移,可能导致元素找不到了,因此要重新找一种方法来扩容
//涉及两个问题:元素怎么搬移和搬移哪些元素(只搬移状态为存在的元素)
//(1)构造新的哈希表,将容量给为新容量(这里是原容量的2倍)
HashTable newHT(_ht.capacity() * 2);
//(2)将原哈希表中状态为存在的元素插入到新的哈希表中
for (size_t i = 0; i < _ht.capacity(); ++i)
{
if (_ht[i]._state == EXIST)
newHT.Insert(_ht[i]._value);
}
//(3)交换两个哈希表
Swap(newHT);
}
}
private:
std::vector> _ht;
size_t _size;//哈希表中有效元素个数
};
void TesthashTable()
{
HashTable ht;
ht.Insert(4);
ht.Insert(7);
ht.Insert(8);
ht.Insert(27);
ht.Insert(9);
ht.Insert(5);
ht.Insert(3);
ht.Insert(1);
ht.Erase(2);
ht.Erase(8);
}
线性探测的优点:实现比较简单
线性探测的缺点:容易产生数据堆积,一旦发生冲突,发生冲突的元素可能会连在一起,因为线性探测解决哈希冲突的方式是从发生哈希冲突的位置挨着往后逐次找的。
线性探测解决哈希冲突:源代码(github):
https://github.com/wangbiy/C-3/commit/39295e148283facd9865e395f476f484ce241ba2
2、二次探测
由于线性探测的缺陷是产生的数据堆积在一起,是因为解决哈希冲突的方式是从发生冲突的位置逐次往后找的,因此我们从这里着手,二次探测为了避免这个问题,找下一个空位置的方法是:比如H0是第一次计算出的哈希地址,Hi代表第i次的哈希地址,Hi=H0+i ^ 2或者Hi=H0-i ^ 2
我们可以利用数学方法将第i+1次的哈希地址算出来,Hi=H0+i ^2,Hi+1=H0+(i+1) ^2,Hi+1-Hi=2i+1,则Hi+1=Hi+2i+1,采用二次探测来解决的话,如果越界不采用线性探测直接将哈希地址置为0的方法,而是只要让哈希地址取模容量即可,这样结果肯定不一样。
线性探测和二次探测多必须考虑负载因子,超过0.7-0.8就增容,增大效率,其中删除是惰性删除,即只标记删除记号。
即实现代码:
bool Insert(const T& val)
{
//检测是否需要扩容
CheckCapacity();
//通过哈希函数计算元素在哈希表中的存储位置
size_t HashAddr = HashFunc(val);
int i = 0;//代表第i次探测
//检测该位置是否可以插入元素
//发生哈希冲突,使用二次探测来解决
while (_ht[HashAddr]._state != EMPTY)
{
if (EXIST == _ht[HashAddr]._state && val == _ht[HashAddr]._value)
{
//就不用插入了,冲突
return false;
}
i++;
if (IsLine)//使用线性探测
{
++HashAddr;
if (HashAddr == _ht.capacity())
HashAddr = 0;//如果找到最后一个还没有找到空位,从头开始
}
else//使用二次探测
{
HashAddr = HashAddr + 2 * i + 1;//利用数学方法
//不能直接使用线性探测越界的方法,因为很有可能造成死循环
HashAddr %= _ht.capacity();//我们采用这种取模的方式来解决越界,这样每次的取模的结果都是不一样的
}
}
//肯定不会让哈希表中的元素放的太多,因为发生冲突的概率会提高,这样哈希表查找的效率就会降低
//所以不用考虑造成死循环的情况
//找到空的位置,进行插入
_ht[HashAddr]._value = val;
_ht[HashAddr]._state = EXIST;
++_size;
return true;
}
int Find(const T& val)
{
size_t HashAddr = HashFunc(val);//计算哈希地址
int i = 0;
while (_ht[HashAddr]._state != EMPTY)//这个位置可能有元素
{
if (_ht[HashAddr]._state == EXIST && _ht[HashAddr]._value == val)
{
return HashAddr;
}
i++;
if (IsLine)//使用线性探测
{
++HashAddr;
if (HashAddr == _ht.capacity())
HashAddr = 0;//如果找到最后一个还没有找到空位,从头开始
}
else//使用二次探测
{
HashAddr = HashAddr + 2 * i + 1;//利用数学方法
//不能直接使用线性探测越界的方法,因为很有可能造成死循环
HashAddr %= _ht.capacity();//我们采用这种取模的方式来解决越界,这样每次的取模的结果都是不一样的
}
}
return -1;//没有这个元素
}
在进行插入和查找时判断使用线性探测还是二次探测,即多增加一个IsLine类型来判断。
但是二次探测的缺陷是0000000,让他的负载因子再小一点即可。
闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷。
#pragma once
#include
using namespace std;
#include
template
//默认哈希表中的元素是唯一的
struct HBNode
{
T _data;
HBNode* _pNext;
HBNode(const T& data)
:_data(data)
, _pNext(nullptr)
{}
};
template
class HashBucket
{
public:
HashBucket(size_t capacity)
:_table(capacity,nullptr)
, _size(0)
{}
~HashBucket()
{
clear();
}
bool Insert(const T& data)
{
CheckCapacity();
size_t bucketNo = HashFunc(data);//计算桶号,即第一个结点
//检测该元素是否在桶中
HBNode* pCur = _table[bucketNo];
while (pCur)
{
if (pCur->_data == data)
return false;
pCur = pCur->_pNext;
}
//插入元素
pCur = new HBNode(data);
//头插,性能比较高
pCur->_pNext = _table[bucketNo];
_table[bucketNo] = pCur;
++_size;
return true;
}
HBNode* Find(const T& data)
{
size_t bucketNo = HashFunc(data);
HBNode* pCur = _table[bucketNo];
while (pCur)
{
if (pCur->_data == data)
return pCur;
pCur = pCur->_pNext;
}
return nullptr;
}
bool Erase(const T& data)
{
size_t bucketNo = HashFunc(data);
HBNode* pCur = _table[bucketNo];
HBNode* pPre = nullptr;
while (pCur)
{
if (pCur->_data == data)//找到了想要删除的数
{
if (pCur == _table[bucketNo])//如果想要删的数的位置在这个桶的第一个结点,也就是_table[bucketNo]
{
_table[bucketNo] = pCur->_pNext;//删除的是第一个结点,也就是头删
}
else//删除的不是第一个结点,就是任意位置删除
{
pPre->_pNext = pCur->_pNext;
}
delete pCur;
--_size;
return true;
}
pPre = pCur;
pCur = pCur->_pNext;
}
return false;
}
size_t Size()const
{
return _size;
}
void Swap(HashBucket& hb)
{
_table.swap(hb._table);
swap(_size, hb._size);
}
void clear()
{
for (size_t bucketNo = 0; bucketNo < _table.capacity(); ++bucketNo)
{
HBNode* pCur = _table[bucketNo];
while (pCur)
{
//头删
_table[bucketNo] = pCur->_pNext;
delete pCur;
pCur = _table[bucketNo];
}
}
_size = 0;
}
void Print()
{
for (size_t i = 0; i < _table.capacity(); ++i)
{
cout << "H[" << i << "]" << ":";
HBNode* pCur = _table[i];
while (pCur)
{
cout << pCur->_data << "----->";
pCur = pCur->_pNext;
}
cout << "NULL" << endl;
}
}
private:
//哈希函数
size_t HashFunc(const T& data)
{
return data % _table.capacity();
}
void CheckCapacity()
{
if (_size == _table.capacity())
{
HashBucket newHB(_size * 2);
for (size_t bucketNo = 0; bucketNo < _table.capacity(); ++bucketNo)
{
HBNode* pCur = _table[bucketNo];
但是这种方式不太好,效率比较低,因为又重新创建了结点
//while (pCur)
//{
// newHB.Insert(pCur->_data);
// pCur = pCur->_pNext;
//}
while (pCur)
{
//1、计算当前结点在新哈希桶中的新桶号
size_t newbucketNo = newHB.HashFunc(pCur->_data);
//2、将结点从哈希表的哈希桶中拆下来
_table[bucketNo] = pCur->_pNext;//将pCur拿出来
//头插
pCur->_pNext = newHB._table[newbucketNo];
newHB._table[newbucketNo] = pCur;
pCur = _table[bucketNo];//让pCur到原哈希桶的下一个节点
}
}
Swap(newHB);
}
}
private:
vector*> _table;//哈希表
size_t _size;
};
但是这个还是有缺陷,由于我们的哈希函数使用的是除留余数法,最好模一个素数,因此如果哈希函数中快速找到具有两倍关系的素数,这样的就比较高效,还有一个问题就是我们实现的开散列只能存放整形元素,现在对他进行改造让哈希桶可以存储任意类型的元素
(1)我们使用一个比当前元素的两倍的素数的集合即可,即common.cpp实现这一功能
(2)我们想要存储任意类型的元素,可以使用仿函数,最终的代码就是:
common.h
#pragma once
size_t GetNextPrime(size_t prime);//获得prime的下一个素数
common.cpp
#include "common.h"
const int PRIMECOUNT = 28;
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 GetNextPrime(size_t prime)
{
size_t i = 0;
for (; i < PRIMECOUNT; ++i)
{
if (primeList[i] > prime)
return primeList[i];
}
return primeList[PRIMECOUNT-1];
}
HashBucket.hpp
#pragma once
#include "common.h"
#include
using namespace std;
#include
#include
template
//默认哈希表中的元素是唯一的
struct HBNode
{
T _data;
HBNode* _pNext;
HBNode(const T& data)
:_data(data)
, _pNext(nullptr)
{}
};
//T--->整形系列
template
class DFDef//实现仿函数
{
public:
T operator()(const T& data)
{
return data;
}
};
//T--->string
size_t BKDRHash(const char* str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = hash * 131 + ch;
}
return hash;
}
class StringToINT
{
public:
size_t operator()(const string& s)
{
return BKDRHash(s.c_str());//直接返回地址
}
};
template >//HF设置为按照默认为整数的方式来处理
class HashBucket
{
public:
HashBucket(size_t capacity=10)
:_table(GetNextPrime(capacity), nullptr)
, _size(0)
{}
~HashBucket()
{
clear();
}
bool Insert(const T& data)
{
CheckCapacity();
size_t bucketNo = HashFunc(data);//计算桶号,即第一个结点
//检测该元素是否在桶中
HBNode* pCur = _table[bucketNo];
while (pCur)
{
if (pCur->_data == data)
return false;
pCur = pCur->_pNext;
}
//插入元素
pCur = new HBNode(data);
//头插,性能比较高
pCur->_pNext = _table[bucketNo];
_table[bucketNo] = pCur;
++_size;
return true;
}
HBNode* Find(const T& data)
{
size_t bucketNo = HashFunc(data);
HBNode* pCur = _table[bucketNo];
while (pCur)
{
if (pCur->_data == data)
return pCur;
pCur = pCur->_pNext;
}
return nullptr;
}
bool Erase(const T& data)
{
size_t bucketNo = HashFunc(data);
HBNode* pCur = _table[bucketNo];
HBNode* pPre = nullptr;
while (pCur)
{
if (pCur->_data == data)//找到了这个桶
{
if (pCur == _table[bucketNo])//找到了
{
_table[bucketNo] = pCur->_pNext;//删除的是第一个结点,也就是头删
}
else//删除的不是第一个结点,就是任意位置删除
{
pPre->_pNext = pCur->_pNext;
}
delete pCur;
--_size;
return true;
}
pPre = pCur;
pCur = pCur->_pNext;
}
return false;
}
size_t Size()const
{
return _size;
}
void Swap(HashBucket& hb)
{
_table.swap(hb._table);
swap(_size, hb._size);
}
void clear()
{
for (size_t bucketNo = 0; bucketNo < _table.capacity(); ++bucketNo)
{
HBNode* pCur = _table[bucketNo];
while (pCur)
{
//头删
_table[bucketNo] = pCur->_pNext;
delete pCur;
pCur = _table[bucketNo];
}
}
_size = 0;
}
void Print()
{
for (size_t i = 0; i < _table.capacity(); ++i)
{
cout << "H[" << i << "]" << ":";
HBNode* pCur = _table[i];
while (pCur)
{
cout << pCur->_data << "----->";
pCur = pCur->_pNext;
}
cout << "NULL" << endl;
}
}
private:
//哈希函数
size_t HashFunc(const T& data)
{
return HF()(data) % _table.capacity();//仿函数的方式
}
void CheckCapacity()
{
size_t oldCapacity = _table.capacity();
if (_size == oldCapacity)
{
HashBucket newHB(GetNextPrime(oldCapacity));//改为两倍的素数关系
for (size_t bucketNo = 0; bucketNo < _table.capacity(); ++bucketNo)
{
HBNode* pCur = _table[bucketNo];
但是这种方式不太好,效率比较低,因为又重新创建了结点
//while (pCur)
//{
// newHB.Insert(pCur->_data);
// pCur = pCur->_pNext;
//}
while (pCur)
{
//1、计算当前结点在新哈希桶中的新桶号
size_t newbucketNo = newHB.HashFunc(pCur->_data);
//2、将结点从哈希表的哈希桶中拆下来
_table[bucketNo] = pCur->_pNext;//将pCur拿出来
//头插
pCur->_pNext = newHB._table[newbucketNo];
newHB._table[newbucketNo] = pCur;
pCur = _table[bucketNo];//让pCur到原哈希桶的下一个节点
}
}
Swap(newHB);
}
}
private:
vector*> _table;//哈希表
size_t _size;
};
void TestHashBucket1()
{
HashBucket ht(10);
ht.Insert(3);
ht.Insert(8);
ht.Insert(4);
ht.Insert(0);
ht.Insert(7);
ht.Insert(13);
ht.Insert(33);
cout << ht.Size() << endl;
ht.Print();
ht.Erase(13);
ht.Print();
ht.Erase(3);
if (nullptr == ht.Find(3))
cout << "3 is not in" << endl;
else
cout << "3 is in" << endl;
ht.clear();
cout << ht.Size() << endl;
}
void TestHashBucket2()
{
HashBucket ht;
ht.Insert("hello");
ht.Insert("CCP");
ht.Insert("I");
ht.Insert("Love");
ht.Insert("You");
ht.Print();
cout << ht.Size() << endl;
ht.Erase("I");
ht.Print();
ht.Erase("hello");
if (nullptr == ht.Find("hello"))
{
cout << "hello is not in" << endl;
}
else
cout << "hello is in" << endl;
cout << ht.Size() << endl;
}
创建两个仿函数类,一个是默认的整形,一个是由字符串转为整形,使用提供的哈希方法BKDRHash来讲字符串转换为整形,然后增加一个模板类型HF,在哈希函数那里按照仿函数调用的方法将元素按照类型实现即可。
(3)接下来我们要给这个开散列增加迭代器操作
我们先封装迭代器类,然后实现即可
//实现迭代器的操作
template >//通过KeyOfValue实现通过key来获取value
class HashBucket;
template //通过KeyOfValue实现通过key来获取value
struct HBIterator
{
typedef HBNode Node;
typedef HBIterator Self;
public:
HBIterator(Node* pNode,HashBucket* ht)//构造函数
:_pNode(pNode)
, _ht(ht)
{}
T& operator*()
{
return _pNode->_data;
}
T* operator->()
{
return &(operator*());
}
//迭代器移动,不能--,因为哈希桶的结构是单链表
//遍历存在的桶,将一个桶的链表遍历完成后再遍历下一个桶的链表
Self& operator++()//前置++
{
Next();
return *this;
}
Self& operator++(int)
{
Self tmp(*this);
Next();
return tmp;
}
void Next()
{
if (_pNode->_pNext)//不为空,当前链表还没有处理完成
{
_pNode = _pNode->_pNext;
}
else//找下一个存在的桶
{
size_t bucketNo = _ht->HashFunc(_pNode->_data) + 1;//哈希函数是哈希桶类的私有成员函数,需要使用友元类
for (; bucketNo < _ht->BucketCount(); ++bucketNo)//BucketCount表示桶的数量
{
if (_ht->_table[bucketNo])
{
_pNode = _ht->_table[bucketNo];
return;
}
}
_pNode = nullptr;
}
}
bool operator!=(const Self& s)const
{
return _pNode != s._pNode && _ht == s._ht;//同一个哈希桶的不同结点
}
bool operator==(const Self& s)const
{
return !(*this != s);
}
private:
Node* _pNode;
HashBucket* _ht;
};
分析:定义成员变量_pNode和哈希桶,(1)对指针的操作(operator* 和operator->)进行重载,然后进行迭代器的移动,最后实现迭代器的比较(记得是同一个哈希桶中结点的比较);(2)在哈希桶类中typedef实现的迭代器名 Iterator,然后实现Begin和End即可。
实现代码:
#pragma once
#include "common.h"
#include
using namespace std;
#include
#include
template
//默认哈希表中的元素是唯一的
struct HBNode
{
T _data;
HBNode* _pNext;
HBNode(const T& data)
:_data(data)
, _pNext(nullptr)
{}
};
//T--->整形系列
template
class DFDef//实现仿函数
{
public:
T operator()(const T& data)
{
return data;
}
};
//T--->string
size_t BKDRHash(const char* str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = hash * 131 + ch;
}
return hash;
}
class StringToINT
{
public:
size_t operator()(const string& s)
{
return BKDRHash(s.c_str());//直接返回地址
}
};
//实现迭代器的操作
template >//通过KeyOfValue实现通过key来获取value
class HashBucket;
template //通过KeyOfValue实现通过key来获取value
struct HBIterator
{
typedef HBNode Node;
typedef HBIterator Self;
public:
HBIterator(Node* pNode,HashBucket* ht)//构造函数
:_pNode(pNode)
, _ht(ht)
{}
T& operator*()
{
return _pNode->_data;
}
T* operator->()
{
return &(operator*());
}
//迭代器移动,不能--,因为哈希桶的结构是单链表
//遍历存在的桶,将一个桶的链表遍历完成后再遍历下一个桶的链表
Self& operator++()//前置++
{
Next();
return *this;
}
Self& operator++(int)
{
Self tmp(*this);
Next();
return tmp;
}
void Next()
{
if (_pNode->_pNext)//不为空,当前链表还没有处理完成
{
_pNode = _pNode->_pNext;
}
else//找下一个存在的桶
{
size_t bucketNo = _ht->HashFunc(_pNode->_data) + 1;//哈希函数是哈希桶类的私有成员函数,需要使用友元类
for (; bucketNo < _ht->BucketCount(); ++bucketNo)
{
if (_ht->_table[bucketNo])
{
_pNode = _ht->_table[bucketNo];
return;
}
}
_pNode = nullptr;
}
}
bool operator!=(const Self& s)const
{
return _pNode != s._pNode && _ht == s._ht;//同一个哈希桶的不同结点
}
bool operator==(const Self& s)const
{
return !(*this != s);
}
private:
Node* _pNode;
HashBucket* _ht;
};
template //HF设置为按照默认为整数的方式来处理
class HashBucket
{
friend HBIterator;//是迭代器类的友元,迭代器类可以访问哈希桶类的私有成员
typedef HashBucket Self;
public:
typedef HBIterator Iterator;//给迭代器取别名
public:
HashBucket(size_t capacity=10)
:_table(GetNextPrime(capacity), nullptr)
, _size(0)
{}
~HashBucket()
{
clear();
}
Iterator Begin()
{
for (size_t bucketNo = 0; bucketNo < BucketCount(); ++bucketNo)
{
if (_table[bucketNo])
return Iterator(_table[bucketNo],this);
}
return End();
}
Iterator End()
{
return Iterator(nullptr, this);
}
bool Insert(const T& data)
{
CheckCapacity();
size_t bucketNo = HashFunc(data);//计算桶号,即第一个结点
//检测该元素是否在桶中
HBNode* pCur = _table[bucketNo];
while (pCur)
{
if (pCur->_data == data)
return false;
pCur = pCur->_pNext;
}
//插入元素
pCur = new HBNode(data);
//头插,性能比较高
pCur->_pNext = _table[bucketNo];
_table[bucketNo] = pCur;
++_size;
return true;
}
HBNode* Find(const T& data)
{
size_t bucketNo = HashFunc(data);
HBNode* pCur = _table[bucketNo];
while (pCur)
{
if (pCur->_data == data)
return pCur;
pCur = pCur->_pNext;
}
return nullptr;
}
bool Erase(const T& data)
{
size_t bucketNo = HashFunc(data);
HBNode* pCur = _table[bucketNo];
HBNode* pPre = nullptr;
while (pCur)
{
if (pCur->_data == data)//找到了这个桶
{
if (pCur == _table[bucketNo])//找到了
{
_table[bucketNo] = pCur->_pNext;//删除的是第一个结点,也就是头删
}
else//删除的不是第一个结点,就是任意位置删除
{
pPre->_pNext = pCur->_pNext;
}
delete pCur;
--_size;
return true;
}
pPre = pCur;
pCur = pCur->_pNext;
}
return false;
}
size_t Size()const
{
return _size;
}
size_t BucketCount()
{
return _table.capacity();
}
void Swap(HashBucket& hb)
{
_table.swap(hb._table);
swap(_size, hb._size);
}
void clear()
{
for (size_t bucketNo = 0; bucketNo < _table.capacity(); ++bucketNo)
{
HBNode* pCur = _table[bucketNo];
while (pCur)
{
//头删
_table[bucketNo] = pCur->_pNext;
delete pCur;
pCur = _table[bucketNo];
}
}
_size = 0;
}
void Print()
{
for (size_t i = 0; i < _table.capacity(); ++i)
{
cout << "H[" << i << "]" << ":";
HBNode* pCur = _table[i];
while (pCur)
{
cout << pCur->_data << "----->";
pCur = pCur->_pNext;
}
cout << "NULL" << endl;
}
}
private:
//哈希函数
size_t HashFunc(const T& data)
{
//HF表示按照仿函数的方式来进行数据类型的实现,如果整形,就是默认的,否则就是字符串转换为整形的元素
return HF()(data) % _table.capacity();//仿函数的方式
}
void CheckCapacity()
{
size_t oldCapacity = _table.capacity();
if (_size == oldCapacity)
{
Self newHB(GetNextPrime(oldCapacity));//改为两倍的素数关系
for (size_t bucketNo = 0; bucketNo < _table.capacity(); ++bucketNo)
{
HBNode* pCur = _table[bucketNo];
但是这种方式不太好,效率比较低,因为又重新创建了结点
//while (pCur)
//{
// newHB.Insert(pCur->_data);
// pCur = pCur->_pNext;
//}
while (pCur)
{
//1、计算当前结点在新哈希桶中的新桶号
size_t newbucketNo = newHB.HashFunc(pCur->_data);
//2、将结点从哈希表的哈希桶中拆下来
_table[bucketNo] = pCur->_pNext;//将pCur拿出来
//头插
pCur->_pNext = newHB._table[newbucketNo];
newHB._table[newbucketNo] = pCur;
pCur = _table[bucketNo];//让pCur到原哈希桶的下一个节点
}
}
Swap(newHB);
}
}
private:
vector*> _table;//哈希表
size_t _size;
};
//这两个测试用例是没有增加KeyOfValue的方法时测试的
void TestHashBucket1()
{
HashBucket ht(10);
ht.Insert(3);
ht.Insert(8);
ht.Insert(4);
ht.Insert(0);
ht.Insert(7);
ht.Insert(13);
ht.Insert(33);
ht.Insert(53);
cout << ht.Size() << endl;
auto it = ht.Begin();
while (it != ht.End())
{
cout << *it<<" ";
++it;
}
cout << endl;
ht.Print();
ht.Erase(13);
ht.Print();
ht.Erase(3);
if (nullptr == ht.Find(3))
cout << "3 is not in" << endl;
else
cout << "3 is in" << endl;
ht.clear();
cout << ht.Size() << endl;
}
void TestHashBucket2()
{
HashBucket ht;
ht.Insert("hello");
ht.Insert("CCP");
ht.Insert("I");
ht.Insert("Love");
ht.Insert("You");
ht.Print();
cout << ht.Size() << endl;
ht.Erase("I");
ht.Print();
ht.Erase("hello");
if (nullptr == ht.Find("hello"))
{
cout << "hello is not in" << endl;
}
else
cout << "hello is in" << endl;
cout << ht.Size() << endl;
}
这个就是哈希桶的实现,也就是开散列的总体实现。
下一节我们将使用这个哈希桶类封装unordered_map结构,此时实现的哈希桶并不能封装unordered_map和unordered_set,因为unordered_map是键值对
结构,unordered_set是key结构,因此我们必须实现KeyOfValue的仿函数,在unordered_map中表示从value中提取出key,在unordered_set表示单个key。