- 本文代码下载:
- 方式1:公众号【多栖技术控小董】回复【3581】免费获取下载链接。
- 方式2:CSDN下载链接https://download.csdn.net/download/qq_41453285/12032247
- 方式3:Github下载链接https://github.com/dongyusheng/Interview-algorithm/tree/master/c%2B%2BAlgorithms(进入之后下载里面的hashTable.zip文件)
一、线性探查法
- 散列表基础介绍与散列冲突见文章:https://blog.csdn.net/qq_41453285/article/details/103517420
- 线性探查法的基本思想:在产生散列冲突时,将产生散列冲突的关键字向后存储
- 当使用了线性探查法之后,整个散列表就被视为一个环形表了
二、图示说明
- 散列函数为f(k)=k%11。且散列表的当前状态如下图所示
- 当要插入58关键字时,求得其起始桶为3=58%11,但是3号桶已经存放了80,那么就将其插入到4的索引处
- 此时再插入一个关键字24,其起始桶为2=24%11,那么直接插入桶2处
- 当再要插入35时,求得其起始桶为2=35%11,但是桶2已经有元素了,那就向后移动,一直移动到桶5处才有地方存储,那么就存储在桶5处
- 这就是线性探查法的基本思想
三、线性探查法下关键字的增加、查询、删除
增加:
- 就是上面图示说明所介绍的思想
查询:
- 1.首先根据要查找的关键字k,首先搜索起始桶f(k),如果起始桶就是要查找的关键字就退出(查找的关键字不存在);如果起始桶为空就退出;如果起始桶不为空,说明产生了散列冲突就进行下一步
- 2.接着向后面遍历,如果查找到了就返回;如果再向后遍历的时候遇到了空桶就退出
- 3.如果向后遍历的时候循环遍历又再次回到起始桶f(k)还是没有找到就退出(查找的关键字不存在)
删除:
- 删除一个关键字需要删除之后保证查询操作可以正常进行,例如下面如果删除了关键字58,那么35这个关键字就永远也找不到了,因为删除58之后,桶4被置位空。当查找35的时候首先查找桶2、再对比桶3、再对比桶4,发现桶4为空,那么就退出查找(此时35显示未查找到,实际上存在于散列表中)
- 删除的方法①:删除之后,从删除的下一个桶开始,逐个检查每个桶,以确定要移动的元素,直到达到一个空桶或者回到删除位置为止就退出移动操作(但是需要注意,不要把一个数对移到它的起始桶之前,否则对这个数的查找就可能失败)。例如删除下面的58之后,就要把35移到桶4处,其余元素不动
- 删除的方法②:为每个桶增加一个域neverUsed,思想如下:
- 在散列表初始化时,每个域被置位true。当一个关键字被插入桶之后,域neverUsed被置为false。当关键字被删除之后,域neverUsed也不会被重新置为true
- 在增加了域neverUsed的散列表中,查找元素在遇到空桶时不会退出查找,而是遇到一个桶为true时才退出。因此删除一个散列表的元素之后,不需要考虑移动元素了,因为查找操作遇到空桶但是桶的域neverUsed为false还继续会向后查找
- 为了提高性能,如果桶中的所有或大多数桶的域neverUsed变为false之后,搜索操作可能会失败。因此当很多桶的域neverUsed变为false之后,就必须重新组织这个散列表
四、随机探查分析
五、选择一个除数D
六、编码实现
头文件定义
#include <iostream> #include <string> using std::cout; using std::endl; using std::cin; using std::string; using std::pair;
异常类的处理
- 哈希表满时抛出
class hashTableFull { public: hashTableFull(std::string theMessage ="The hash table is full") { message = theMessage; } const char* what() { return message.c_str(); } private: std::string message; };
关键字映射整数模板类
- 如果插入的关键字为字符串,那么就需要把关键字映射为一个整数,然后运用散列函数求得起始桶,然后进行插入
- 下面建立了一系列的模板类,用来针对传入的关键字数据类型,将其转换为一个非负的整数,然后运用于散列函数
- 详细介绍也可以参见文章中的七:https://blog.csdn.net/qq_41453285/article/details/103517420
template<class K> class hash; template<> class hash<std::string> { public: std::size_t operator()(const std::string theKey)const { unsigned long hashValue = 0; int length = (int)theKey.length(); for (int i = 0; i < length; i++) { hashValue = hashValue * 5 + theKey.at(i); } return std::size_t(hashValue); } }; template<> class hash<int> { public: std::size_t operator()(const int theKey) const { return std::size_t(theKey); } }; template<> class hash<long> { public: std::size_t operator()(const long theKey) const { return std::size_t(theKey); } };
类定义
template<class K,class E> class hashTable { public: hashTable(int theDivisor = 11); ~hashTable(); bool empty()const; int size()const; std::pair<const K, E>* find(const K& theKey)const; void insert(const std::pair<const K,E>& thePair); void output(std::ostream& out)const; //打印散列表 private: int search(const K& theKey)const; private: std::pair<const K, E>** table; //散列表 hash<K> hash; //把类型K映射为一个整数 int dSize; //字典中的数对个数 int divisor; //散列函数除数 };
构造函数
template<class K, class E> hashTable<K, E>::hashTable(int theDivisor = 11) { this->divisor = theDivisor; this->dSize = 0; //将整个散列表置空 this->table = new std::pair<const K, E>*[theDivisor]; for (int i = 0; i < theDivisor; ++i) this->table[i] = nullptr; }
析构函数
template<class K, class E> hashTable<K, E>::~hashTable() { delete[] this->table;//释放数组 this->table = nullptr; }
empty()、size()函数
template<class K, class E> bool hashTable<K, E>::empty()const { return (this->dSize == 0); } template<class K, class E> int hashTable<K, E>::size()const { return this->dSize; }
search函数
- 这个函数返回一个桶序号b,有3中用途
- ①table[b]是一个指针,指向关键字为theKey的数对
- ②散列表没有关键字为theKey的数对,且table[b]=nullptr,则可以把关键字theKey插入散列表
- ③散列表没有关键字为theKey的数对,但table[b]!=nullptr,table[b]!=theKey,表示表已满
template<class K, class E> int hashTable<K, E>::search(const K& theKey)const { int i = ((int)this->hash(theKey) / this->divisor); int j = i; //循环遍历 do { //如果桶的关键字为要查找的关键字或者桶为空退出,返回桶索引 if ((this->table[j]->first == theKey) || (this->table[j] == nullptr)) return j; j = ((j + 1) % this->divisor); } while (j != i); return j; }
find函数
template<class K, class E> std::pair<const K, E>* hashTable<K, E>::find(const K& theKey)const { int b = this->search(theKey); //没有查找到 if ((this->table[b] == nullptr) || this->table[b]->first != theKey) return nullptr; //查找到,返回 return this->table[b]; }
insert函数
template<class K, class E> void hashTable<K, E>::insert(const std::pair<const K, E>& thePair) { int b = this->search(thePair.first); //如果为空就插入 if (this->table[b] == nullptr) { this->table[b] = new std::pair<const K, E>(thePair); this->dSize++; } else{ //如果关键字与插入的关键字相同,更新至 if (this->table[b]->first == thePair.first) this->table[b]->second = thePair.second; else//满了,抛出异常 throw hashTableFull(); } }
主函数
int main() { hashTable<int,int>* myHashTable = new hashTable<int, int>(11); myHashTable->insert(std::pair<int,int>(80,1)); myHashTable->insert(std::pair<int, int>(40, 2)); myHashTable->insert(std::pair<int, int>(65, 3)); myHashTable->output(std::cout); std::cout << std::endl; myHashTable->insert(std::pair<int, int>(58, 4)); myHashTable->insert(std::pair<int, int>(24, 5)); myHashTable->output(std::cout); std::cout << std::endl; myHashTable->insert(std::pair<int, int>(35, 5)); myHashTable->output(std::cout); std::cout << std::endl; return 0; }
- :前为索引号,:后为关键字值
性能分析