【散列】散列表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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!