浅谈哈希表
定义
散列表,通过散列函数把键值映射到散列表中的一个位置,根据关键字可以直接访问内存存储位置的数据结构。
实现
散列函数
-
直接寻址法:一次线性函数映射,若冲突则向下一位移动
-
平方取中法:键值取平方,再取中间的几位数作哈希地址
-
除数取余法 :关键字被小于等于表长度的数除取得余数作散列地址。一般取作素数,可使散列表均匀。
冲突解决
开放定址法(线性探测再散列,二次探测再散列),再散列法,拉链法。
装填因子
填入表中的元素个数/散列表的长度
以下实现由除数取余法+拉链法的散列表
节点定义
struct Node { //节点结构体
Node(int i) :key(i), next(nullptr) {}
int key;
Node* next; //维护映射到统一的地址的key
};
哈希表类
class HashTable { //哈希表
public:
HashTable(int);
~HashTable() {
clear();
delete[] tableList;
}
void insert(int);
void remove(int);
Node* find(int);
void clear();
unsigned int hash(int);
void display();
private:
int tableSize;
Node** tableList;
};
构造
哈希函数的二维Node数组,直观来说,第一维是纵向的,第二维是横向的。哈希表的建立,就是在一维上指定素数大小的Node数组,再初始化二维的数组为nullptr
HashTable::HashTable(int n) {
tableSize = setSize(n); //指定哈希表长度为素数
tableList = new Node * [tableSize]; //哈希表维护一个Node链表
for (int i = 0; i < tableSize; ++i) //初始化Node链表
tableList[i] = nullptr;
}
插入
首先是对键值哈希,然后找到一维位置,判断哈希表中是否已有该记录,若无,则采用前插法,将新节点置顶。(若后插法需要while循环到nullptr的节点处)
void HashTable::insert(int k) {
unsigned int index = hash(k);
Node* pos = find(k); //寻找插值位置
if (pos == nullptr) {
Node* node = new Node(k);
node->next = tableList[index]; //插在前面
tableList[index] = node; //移动头指针
}
}
另外,允许重复键值的改动是
while (pos != nullptr) //将 if (pos == nullptr) 条件换成这个循环体
pos = pos->next;
删除
- 若删除的是置顶元素,则将对应一维Node指针指向下一个元素,
delete
置顶元素 - 若删除元素在中间或末尾,临时对象保存删除节点,将指针越过删除节点,释放临时节点。
- 若哈希表中无此元素,无操作。
void HashTable::remove(int k) {
unsigned int index = hash(k);
Node* node = tableList[index];
if (node != nullptr) {
if (node->key == k) { //case1:头节点元素
tableList[index] = node->next;
delete node;
}
else {
while (node != nullptr && node->next != nullptr) { //case2:中间或结尾元素
if (node->next->key == k) { //case3:数据不在哈希表中,无操作
Node* temp = node->next;
node->next = temp->next;
delete temp;
}
node = node->next;
} //end while
} //end else
} //end if
}
源码
#include
#include
#include
using namespace std;
struct Node { //节点结构体
Node(int i) :key(i), next(nullptr) {}
int key;
Node* next; //维护映射到统一的地址的key
};
class HashTable { //哈希表
public:
HashTable(int);
~HashTable() {
clear();
delete[] tableList;
}
void insert(int);
void remove(int);
Node* find(int);
void clear();
unsigned int hash(int);
void display();
private:
int tableSize;
Node** tableList;
};
bool isPrime(int number)
{
if (number == 1) //1不是素数
return false;
if (number == 2) //2是素数
return true;
for (int i = 2; i * i <= number; i++)
if (number % i == 0)
return false; //找到因子就返回false
return true;
}
unsigned int setSize(int n) { //以大于等于N的素数为哈希表的大小
while (true) {
if (isPrime(n++)) //选择素数可以减少冲突
return --n;
}
}
HashTable::HashTable(int n) {
tableSize = setSize(n); //指定哈希表长度为素数
tableList = new Node * [tableSize]; //哈希表维护一个Node链表
for (int i = 0; i < tableSize; ++i) //初始化Node链表
tableList[i] = nullptr;
}
unsigned int HashTable::hash(int key) { //除数取余法
return key % tableSize;
}
//unsigned int HashTable::hash(const string& key){ //string hash
// unsigned int hashVal = 0;
// for (char ch : key)
// hashVal = 37 * hashVal + ch; //key[0]+key[1]
//
// return hashVal % tableSize;
//
//}
Node* HashTable::find(int k) { //查找时间=计算散列函数值所需要的常数时间+遍历链表所用的时间
unsigned int index = hash(k);
Node* curList = tableList[index];
while (curList != nullptr && curList->key != k)
curList = curList->next;
return curList;
}
void HashTable::insert(int k) {
unsigned int index = hash(k);
Node* pos = find(k); //寻找插值位置
//while (pos != nullptr)
// pos = pos->next;
if (pos == nullptr) {
Node* node = new Node(k);
node->next = tableList[index]; //插在前面
tableList[index] = node; //移动头指针
}
}
void HashTable::remove(int k) {
unsigned int index = hash(k);
Node* node = tableList[index];
if (node != nullptr) {
if (node->key == k) { //case1:头节点元素
tableList[index] = node->next;
delete node;
}
else {
while (node != nullptr && node->next != nullptr) { //case2:中间或结尾元素
if (node->next->key == k) { //case3:数据不在哈希表中,无操作
Node* temp = node->next;
node->next = temp->next;
delete temp;
}
node = node->next;
} //end while
} //end else
} //end if
}
void HashTable::clear() {
Node* node = nullptr;
for (int i = 0; i < tableSize; ++i) {
node = tableList[i];
while (node != nullptr) {
Node* temp = node;
node = node->next;
delete temp;
} //end while
} //end for
tableSize = 0;
}
void HashTable::display() {
if (tableSize == 0) {
cout << "Hash table is empty!" << endl;
return;
}
cout << "Hash table:" << endl;
for (int i = 0; i < tableSize; i++)
{
cout << i << " -> ";
if (tableList[i] != nullptr) {
Node* node = tableList[i];
while (node != nullptr)
{
cout << node->key << " -> ";
node = node->next;
}
cout << endl;
} //end if
else
cout << endl;
} //end for
}
int main()
{
HashTable* hashTable = new HashTable(7);
random_device rd;
mt19937 mt(rd());
for (int i = 0; i < 13; i++)
hashTable->insert(mt() % 20);
hashTable->display();
if (hashTable->find(4) != nullptr)
cout << "element 4 exits!" << endl;
hashTable->clear();
hashTable->display();
}