#include<iostream>
using namespace std;
/*
一、散列表由来
散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一组扩展,由数组演化而来。
二、散列思想
我们用“键值”或者“关键字”来标识一个对象。把“键值”或者“关键字”转化为数组下标的映射方法就叫做“散列函数”,经过散列函数计算的值称为“散列值”。
三、散列函数设计的基本要求
①散列值为一个非负整数值,对应数组下标
②if key1=key2 then hash(key1)=hash(key2)
③if key1≠key2 then hash(key2)≠hash(key2)
如果无法满足③,就是发生了散列冲突(ps:实际上要找一个满足③的散列函数几乎是不可能的)。
四、解决散列冲突方法
1. 开放寻址法(opeing addressing)
①线性探测(Linear Probing):当我们往散列表中插入数据时,如果某个数据经过散列函数散列之后,
存储位置已经被占用,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。
ps:采用线性探测方法解决冲突时,删除操作不能单存地把删除地元素设置为空,否则查找操作将可能失败。
②二次探测(Quadratic probing)和双重散列(double hashing)
2. 链表法(chaining)
*/
class HashNode {
public:
int value;
int key;
HashNode(int key, int value) : value(value), key(key) {}
};
class HashMap {
HashNode** arr;
int capacity;
int size;
HashNode* dummy; // 虚拟节点代表被删除的节点
public:
HashMap() {
capacity = 20;
size = 0;
arr = new HashNode * [capacity];
for (int i = 0; i < capacity; i++) {
arr[i] = nullptr;
}
dummy = new HashNode(-1, -1);
}
~HashMap() {
for (int i = 0; i < capacity; i++) {
if (arr[i] != nullptr) {
delete arr[i];
}
}
delete[] arr;
}
int hashCode(int key) {
return key % capacity;
}
void insertNode(int key, int value) {
HashNode* temp = new HashNode(key, value);
int hashIndex = hashCode(key);
// 线性法解决碰撞
while (arr[hashIndex] != nullptr && arr[hashIndex]->key != key && arr[hashIndex]->key != -1) {
// 如果容量已满会造成死循环
hashIndex++;
hashIndex %= capacity;
}
// 找到了存有相同键值的元素,先将该元素delete
if (arr[hashIndex] != nullptr) {
delete arr[hashIndex];
size--;
}
arr[hashIndex] = temp;
size++;
}
int deleteNode(int key) {
int hashIndex = hashCode(key);
while (arr[hashIndex] != nullptr && arr[hashIndex]->key != key) {
hashIndex++;
hashIndex %= capacity;
}
if (arr[hashIndex] == nullptr) {
return -1;
}
int temp = arr[hashIndex]->value;
delete arr[hashIndex];
arr[hashIndex] = dummy;
size--;
return temp;
return -1;
}
int search(int key) {
int hashIndex = hashCode(key);
while (arr[hashIndex] != nullptr && arr[hashIndex]->key != key) {
hashIndex++;
hashIndex %= capacity;
}
if (arr[hashIndex] == nullptr) {
return -1;
}
return arr[hashIndex]->value;
}
int sizeOfMap() {
return size;
}
bool isEmpty() {
return size == 0;
}
void display() {
for (int i = 0; i < capacity; i++) {
if (arr[i] != NULL) {
cout << "key = " << arr[i]->key
<< " value = " << arr[i]->value << endl;
}
}
}
};
int main() {
HashMap* h = new HashMap();
cout << h->isEmpty() << endl;
h->insertNode(1, 1);
h->insertNode(2, 2);
h->insertNode(2, 3);
h->insertNode(5, 8);
h->insertNode(6, 9);
h->display();
cout << h->isEmpty() << endl;
cout << h->sizeOfMap() << endl;
cout << h->search(2) << endl;
h->display();
cout << h->deleteNode(7) << endl;
cout << h->deleteNode(5) << endl;
h->display();
delete h;
return 0;
}
ps:本人C++小白,暂时无法实现泛型的hashtable,在这里使用int作为键和值,其次代码通过了基本的测试,但内存有无泄露也不太清楚,查阅网上的实现发现都有问题,但难以改正。
主要参考
Implementing own Hash Table with Open Addressing Linear Probing in C++ (感觉这个人写的代码,指针好像没有释放,也没有析构函数,感觉代码很多问题,不知道是我自己的理解问题还是确实有问题)
C++ Program to Implement Hash Tables(这位老哥写的还行,但也觉得有问题,特别是删除操作,他直接删除了没有使用标记,后面查找好像会出问题)。