竹_

【散列】散列表HashTable线性探测法类模板的实现

线性探测法

一般说来,对于不使用分离链接的散列表来说,其装填因子应该低于 λ=0.5,这样的表叫作探测散列表(probing hash table)。

如上图所示,当插入关键字68时,h(68)=68%11=2,与23冲突,于是被放入到下一个空闲位置4;插入11时,h(11)=0,与55冲突,向后寻找并存入空闲位置5。。。。只要表足够大,总能找到一个空闲位置,但花费的时间是相当多的。

可以看出,插入和不成功查找需要相同次数的探测,且成功查找应该比不成功查找平均花费较少的时间。

平方探测法

平方探测法是消除线性探测中一次聚集问题的冲突解决办法 – 冲突函数为二次的探测方法,流行的选择是f(i)=i2

如果使用平方探测,且表的大小是素数,那么当表至少有一半是空的时候,总能够插入一个新的元素。

再散列

对于使用平方探测的开放定址散列法(open addressing hashing),若散列表装的太满,则操作的运行时间将消耗过长,且插入操作可能失败,这可能发生在有太多的删除和插入混杂的场合。
此时,一种解决办法是建立另外一个大约两倍大的表(而且使用一个相关的新散列函数),扫描整个原始散列表,计算每个(未删除的)元素的新散列值并将其插入到新表中。

再散列可以用平方探测以多种方法实现:

(1)只要表满到一半就再散列
(2)只有当插入失败时才再散列
(3)途中策略:当散列表到达某一特定的装填因子时进行再散列。

代码实现

#include<vector>
#include<string>
int nextPrime(int n);
//使用函数对象,让散列函数只用对象作为参数并返回适当的整形量
template<typename T>
class _hash
{
public:
size_t operator()(const T& key) {
size_t hashVal = 0;
T tmp = key;
while (tmp > 0) {
hashVal = 37 * hashVal + tmp % 10;
tmp /= 10;
}
return hashVal;
}
};
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;
}
};
//使用探测方法的散列表的类接口,包括内嵌的HashEntry类
template<typename HashedObj>
class HashTable {
public:
explicit HashTable(int size = 101):array(nextPrime(size))
{
makeEmpty();
}
bool contains(const HashedObj& x)const {
return isActive(findPos(x));
}
void makeEmpty() {
currentSize = 0;
for (auto& entry : array)
entry.info = EMPTY;
}
bool insert(const HashedObj& x) {
//将x作为active插入
int currentPos = findPos(x);
if (isActive(currentPos))
return false;
array[currentPos].element = x;
array[currentPos].info = ACTIVE;
//再散列
if (++currentSize > array.size() / 2) //装填因子超过0.5,则表是满的
rehash();
return true;
}
bool insert(HashedObj&& x) {
//将x作为active插入
int currentPos = findPos(x);
if (isActive(currentPos))
return false;
array[currentPos].element = std::move(x);
array[currentPos].info = ACTIVE;
//再散列
if (++currentSize > array.size() / 2) //装填因子超过0.5,则表是满的
rehash();
return true;
}
bool remove(const HashedObj& x) {
int currentPos = findPos(x);
if (!isActive(currentPos))
return false;
array[currentPos].info = DELETED;
return true;
}
enum EntryType{ACTIVE,EMPTY,DELETED};
private:
struct HashEntry {
HashedObj element;
EntryType info;
HashEntry(const HashedObj& e = HashedObj{}, EntryType i = EMPTY)
:element(e), info{ i }{}
HashEntry(HashedObj&& e, EntryType i = EMPTY)
:element(e), info{ i }{}
};
std::vector<HashEntry>array;
int currentSize;
bool isActive(int currentPos)const {
return array[currentPos].info == ACTIVE;
}
int findPos(const HashedObj& x)const {
int offset = 1;
int currentPos = myhash(x);
/*平方探测快速方法,由平方消解函数的定义可知,f(i)=f(i-1)+2i-1,因此,下一个要尝试的
* 单元离上一个被试过的单元有一段距离,而这个距离在连续探测中增2。
*/
while (array[currentPos].info != EMPTY && array[currentPos].element != x) {
currentPos += offset; //计算第i次探测
offset += 2;
if (currentPos >= array.size()) //新的定位越过数组,需要将它拉回到数组范围内
currentPos -= array.size();
}
return currentPos;
}
void rehash() {
std::vector<HashEntry> oldArray = array;
//创建新的两倍大小的空表
array.resize(nextPrime(2 * oldArray.size()));
for (auto& entry : array)
entry.info = EMPTY;
//复制整个表
currentSize = 0;
for (auto& entry : oldArray)
if (entry.info == ACTIVE)
insert(std::move(entry.element));
}
size_t myhash(const HashedObj& x)const {
static _hash<HashedObj> hf;
return hf(x) % array.size();
}
};
/**
* 判断素数
*/
bool isPrime(int n)
{
if (n == 2 || n == 3)
return true;
if (n == 1 || n % 2 == 0)
return false;
for (int i = 3; i * i <= n; i += 2)
if (n % i == 0)
return false;
return true;
}
/**
* 返回比n大的下一个素数
* 假设 n > 0.
*/
int nextPrime(int n)
{
if (n % 2 == 0)
++n;
for (; !isPrime(n); n += 2)
;
return n;
}
posted @   aw11  阅读(89)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示