《C++》哈希表解析与实现

一、哈希表基础概念

1.1 什么是哈希表

  • 哈希表(Hash Table)是一种使用哈希函数组织数据的数据结构,它通过把关键码值映射到表中一个位置来访问记录,以实现快速查找。简单来说,哈希表就是一个"键-值"对的集合,能够根据键快速找到对应的值。

  • 哈希表的核心思想是:用空间换时间。它通过预先分配一个较大的数组,然后使用哈希函数将键转换为数组下标,从而实现平均时间复杂度为O(1)的查找、插入和删除操作。

1.2 为什么需要哈希表

在没有哈希表的情况下,我们通常使用数组或链表来存储数据:

  • 数组:查找快(O(1)),但插入和删除慢(O(n))
  • 链表:插入和删除快(O(1)),但查找慢(O(n))

哈希表结合了两者的优点,在大多数情况下都能提供O(1)时间复杂度的操作,极大地提高了数据处理的效率。

1.3 哈希表的常见应用

哈希表在编程中无处不在,典型应用包括:

  1. 字典实现:C++中的unordered_map
  2. 网页去重:检测重复URL
  3. 拼写检查:快速查找单词是否正确

二、哈希表核心原理

2.1 哈希函数设计

2.1.1 优秀哈希函数的特性
  • 确定性:相同输入总是产生相同输出

  • 均匀性:哈希值应均匀分布

  • 高效性:计算速度快

  • 抗碰撞性:不同输入产生相同输出的概率低

2.1.2 常见哈希函数
  • ​​直接定址法​​:Hash(key) = a*key + b

  • 除留余数法​​:Hash(key) = key % p(p最好是质数)

  • ​​平方取中法​​:取key平方的中间几位

  • ​​折叠法​​:将key分成几部分后相加

  • ​​随机数法​​:Hash(key) = random(key)

2.2 哈希冲突解决

2.2.1 开放定址法(闭散列)
  • 线性探测​​:依次检查下一个位置

  • ​​二次探测​​:使用二次方程计算探测位置

  • 双重哈希​​:使用第二个哈希函数

2.2.2 链地址法 (C++ STL采用此方法)
  • 每个桶位置维护一个链表

  • 冲突元素追加到链表末尾

2.3 装载因子与扩容

装载因子(Load Factor)是哈希表中已存储元素数量与哈希表大小的比值。当装载因子超过某个值(通常为0.7-0.8)时,哈希表的性能会显著下降,此时需要进行扩容。扩容的操作包括以下步骤:

  1. 创建一个更大的哈希表(通常是原大小的2倍)
  2. 重新计算所有元素的哈希值并插入新表
  3. 释放原表的空间

三、C++中的哈希表实现

3.1 STL中的unordered_map

C++11引入了unordered_map,它是基于哈希表实现的关联容器,使用非常方便:

#include <unordered_map>
#include <string>

std::unordered_map<std::string, int> wordCount;

// 插入元素
wordCount["apple"] = 5;
wordCount.insert({"banana", 3});

// 访问元素
std::cout << "apple count: " << wordCount["apple"] << std::endl;

// 遍历
for (const auto& pair : wordCount) {
    std::cout << pair.first << ": " << pair.second << std::endl;
}

3.2 自定义哈希函数

当使用自定义类型作为键时,需要提供哈希函数和相等比较函数:

struct Person {
    std::string name;
    int age;
};

// 自定义哈希函数
struct PersonHash {
    size_t operator()(const Person& p) const {
        return std::hash<std::string>()(p.name) ^ std::hash<int>()(p.age);
    }
};

// 自定义相等比较
struct PersonEqual {
    bool operator()(const Person& p1, const Person& p2) const {
        return p1.name == p2.name && p1.age == p2.age;
    }
};

std::unordered_map<Person, std::string, PersonHash, PersonEqual> personMap;

3.3 性能调优

unordered_map提供了一些调整性能的参数:

  • bucket_count:桶的数量
  • load_factor:当前装载因子
  • max_load_factor:最大允许装载因子
std::unordered_map<std::string, int> myMap;

// 预留空间,避免频繁扩容
myMap.reserve(1000);

// 设置最大装载因子
myMap.max_load_factor(0.75);

// 获取桶数量
std::cout << "Bucket count: " << myMap.bucket_count() << std::endl;

四、手动实现哈希表

4.1 基础哈希表实现

#include <vector>
#include <list>

template <typename K, typename V>
class HashTable {
private:
    struct KeyValue {
        K key;
        V value;
        KeyValue(const K& k, const V& v) : key(k), value(v) {}
    };

    std::vector<std::list<KeyValue>> table;
    size_t size;
    
    size_t hash(const K& key) const {
        return std::hash<K>()(key) % table.size();
    }

public:
    HashTable(size_t initialSize = 101) : table(initialSize), size(0) {}
    
    void insert(const K& key, const V& value) {
        size_t index = hash(key);
        for (auto& kv : table[index]) {
            if (kv.key == key) {
                kv.value = value;
                return;
            }
        }
        table[index].emplace_back(key, value);
        size++;
    }
    
    bool find(const K& key, V& value) const {
        size_t index = hash(key);
        for (const auto& kv : table[index]) {
            if (kv.key == key) {
                value = kv.value;
                return true;
            }
        }
        return false;
    }
    
    void remove(const K& key) {
        size_t index = hash(key);
        for (auto it = table[index].begin(); it != table[index].end(); ++it) {
            if (it->key == key) {
                table[index].erase(it);
                size--;
                return;
            }
        }
    }
};

4.2 扩容机制实现

添加自动扩容功能,当装载因子过高时自动扩展哈希表:

void checkLoadFactor() {
    double loadFactor = static_cast<double>(size) / table.size();
    if (loadFactor > 0.7) {
        rehash(table.size() * 2);
    }
}

void rehash(size_t newSize) {
    std::vector<std::list<KeyValue>> newTable(newSize);
    for (auto& bucket : table) {
        for (auto& kv : bucket) {
            size_t newIndex = std::hash<K>()(kv.key) % newSize;
            newTable[newIndex].push_back(kv);
        }
    }
    table = std::move(newTable);
}

4.3 迭代器实现

为哈希表添加迭代器支持,使其更符合STL风格:

class iterator {
    typename std::vector<std::list<KeyValue>>::iterator vecIt;
    typename std::list<KeyValue>::iterator listIt;
    std::vector<std::list<KeyValue>>* table;
    
public:
    iterator(std::vector<std::list<KeyValue>>* t, 
             typename std::vector<std::list<KeyValue>>::iterator vi,
             typename std::list<KeyValue>::iterator li)
        : table(t), vecIt(vi), listIt(li) {}
    
    KeyValue& operator*() { return *listIt; }
    KeyValue* operator->() { return &(*listIt); }
    
    iterator& operator++() {
        ++listIt;
        if (listIt == vecIt->end()) {
            ++vecIt;
            while (vecIt != table->end() && vecIt->empty()) {
                ++vecIt;
            }
            if (vecIt != table->end()) {
                listIt = vecIt->begin();
            }
        }
        return *this;
    }
    
    bool operator!=(const iterator& other) const {
        return vecIt != other.vecIt || listIt != other.listIt;
    }
};

iterator begin() {
    auto vecIt = table.begin();
    while (vecIt != table.end() && vecIt->empty()) {
        ++vecIt;
    }
    if (vecIt != table.end()) {
        return iterator(&table, vecIt, vecIt->begin());
    }
    return end();
}

iterator end() {
    return iterator(&table, table.end(), typename std::list<KeyValue>::iterator());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值