哈希表

0 阅读3分钟

基础

哈希表是一种基于键值对(key-value)存储的数据结构,底层通常是一个数组。

它通过哈希函数将Key映射为数组下标,从而实现数据的快速访问。

在理想情况下,查找、插入、删除的时间复杂度是O(1),但由于不同key可能映射到同一个位置,会产生哈希冲突,因此需要额外的冲突解决策略。

常见的哈希冲突解决方法有两类:

  1. 链地址法:每个数字位置存一个链表,冲突元素挂在同一个桶中(哈希桶)。优点:实现简单,对扩容不敏感;缺点:极端情况下会退化为O(N)。

  2. 开放寻址法:所有元素都存在数组中,冲突时通过探测寻找下一个空位,常见方式有:

    1. 线性探测:+1 +1 +1(容易产生聚集)
    2. 二次探测:+1^2 +2^2(减少聚集)
    3. 双重哈希:再用一个哈希函数
    4.   缺点:容易产生聚集问题,影响性能!

在java的HashMap中,当某个桶的链表长度超过一定阈值(默认8)时,会将链表转换为红黑树。

原因:链表查找的时间复杂度是O(N),而红黑树是O(logN),可以显著提高查询效率,避免性能退化。同时,当元素减少到一定程度(比如小于6)时,还会退化回链表,以减少红黑树的维护成本。

实现

  1. 数据结构:一个数组,数组的每个元素是一个链表的头节点指针,每个链表节点存储

    1. key
    2. Value
    3. next指针
  2. 成员变量

    1. vector<Node*> table 哈希桶数组
    2. Int size 当前元素个数
    3. Int capacity 数组大小
    4. Float load_factor 负载因子(用于扩容)
  3. 哈希函数:使用取模运算:index = key % capacity

  4. 基本操作:

    1. push(key, value) 插入(头插法)
    2. get(key) 查找(遍历链表)
    3. remove(key) 删除
    4. 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;
    }
};