基础
哈希表是一种基于键值对(key-value)存储的数据结构,底层通常是一个数组。
它通过哈希函数将Key映射为数组下标,从而实现数据的快速访问。
在理想情况下,查找、插入、删除的时间复杂度是O(1),但由于不同key可能映射到同一个位置,会产生哈希冲突,因此需要额外的冲突解决策略。
常见的哈希冲突解决方法有两类:
-
链地址法:每个数字位置存一个链表,冲突元素挂在同一个桶中(哈希桶)。优点:实现简单,对扩容不敏感;缺点:极端情况下会退化为O(N)。
-
开放寻址法:所有元素都存在数组中,冲突时通过探测寻找下一个空位,常见方式有:
- 线性探测:+1 +1 +1(容易产生聚集)
- 二次探测:+1^2 +2^2(减少聚集)
- 双重哈希:再用一个哈希函数
- 缺点:容易产生聚集问题,影响性能!
在java的HashMap中,当某个桶的链表长度超过一定阈值(默认8)时,会将链表转换为红黑树。
原因:链表查找的时间复杂度是O(N),而红黑树是O(logN),可以显著提高查询效率,避免性能退化。同时,当元素减少到一定程度(比如小于6)时,还会退化回链表,以减少红黑树的维护成本。
实现
-
数据结构:一个数组,数组的每个元素是一个链表的头节点指针,每个链表节点存储
- key
- Value
- next指针
-
成员变量
- vector<Node*> table 哈希桶数组
- Int size 当前元素个数
- Int capacity 数组大小
- Float load_factor 负载因子(用于扩容)
-
哈希函数:使用取模运算:
index = key % capacity -
基本操作:
push(key, value)插入(头插法)get(key)查找(遍历链表)remove(key)删除rehash()扩容、重映射
#include <vector>
struct Node
{
int key;
int value;
Node* next;
Node(int k, int v) : key(k), value(v), next(nullptr) {}
};
class HashTable
{
public:
HashTable(int cap = 16, float lf = 0.75f) : capacity(cap),
load_factor(lf),
size(0)
{
table.resize(capacity, nullptr);
}
void put(int k, int v)
{
//先查再插,更新or插入
int index = hash(k);
Node* cur = table[index];
while (cur != nullptr)
{
if (cur->key == k)
{
cur->value = v;
return;
}
cur = cur->next;
}
//不存在,头插
Node* newnode = new Node(k, v);
newnode->next = table[index];
table[index] = newnode;
++size;
if ((float)size / capacity >= load_factor) rehash();
}
int get(int k)
{
int index = hash(k);
Node* cur = table[index];
while(cur != nullptr)
{
if (cur->key == k) return cur->value;
cur = cur->next;
}
return -1;
}
void rehash()
{
//当哈希表负载因子超过阈值,通过扩大底层数组容量,并重新将元素按照新的容量及逆行哈希映射的过程
//容量太小->冲突多->链表变长->性能下降
//扩容->降低冲突->保持O(1)平均复杂度
std::vector<Node*> old_table = table;
capacity *= 2; //也就改变了哈希函数
table.clear();
table.resize(capacity, nullptr);
size = 0;
for (auto head : old_table)
{
Node* cur = head;
while (cur != nullptr)
{
put(cur->key, cur->value); //复用代码
cur = cur->next;
}
}
}
void remove(int k)
{
int index = hash(k);
Node* cur = table[index];
Node* prev = nullptr;
while (cur != nullptr)
{
if (cur->key == k)
{
if (prev == nullptr) //删除头节点
{
table[index] = cur->next;
}
else
{
prev->next = cur->next;
}
delete cur;
--size;
return;
}
prev = cur;
cur = cur->next;
}
}
private:
std::vector<Node*> table;
int size;
int capacity;
float load_factor; //负载因子(size/capacity)超过本load_factor时扩容,进行rehash
int hash(int key) //哈希函数在这里
{
return key % capacity;
}
};