c++ 手写STL篇(三)实现hashtable

在c++中,hanshtable是一个重要的底层数据结构,无序容器(unordered_map \ unordered_set)底层都是hashtable实现的。

一、什么是 Hash Table,为什么它如此重要?

在正式开始实现之前,咱们得先搞清楚 Hash Table 到底是个啥。想象一下,你有一个超级大的图书馆,里面摆满了各种各样的书籍。如果每次找一本书都要从第一排书架开始一本一本地找,那得耗费相当长的时间。这时候,Hash Table 就像是图书馆的智能索引系统,它能让你快速地找到你想要的书。

Hash Table 是一种根据键(key)直接访问内存存储位置的数据结构。它通过一个哈希函数(hash function),将键映射到一个固定大小的数组中的某个位置,这个位置就像是图书馆里书的具体书架编号。这样一来,当你要查找某个键对应的值时,就可以直接通过哈希函数计算出位置,然后快速找到它,而不需要像在链表中那样逐个元素地查找。

Hash Table 的重要性不言而喻。在很多实际应用场景中,比如数据库的索引、缓存系统、编译器的符号表等等,都广泛地使用了 Hash Table。因为它能提供非常高效的查找、插入和删除操作,平均时间复杂度可以达到 O (1),这在处理大量数据时能大大提高程序的性能。

二、哈希表工作原理剖析

哈希表的工作流程主要涉及哈希函数、哈希冲突处理以及存储结构三个关键环节。

(一)哈希函数

哈希函数是哈希表实现的核心组件,它负责将任意类型的键转换为一个固定范围的整数值,该整数值用于确定键值对在哈希表中的存储位置。一个优秀的哈希函数需要满足以下特性:

  1. 确定性:相同的键经过哈希函数计算,必须得到相同的哈希值。
  2. 均匀性:能够将键尽可能均匀地分布到哈希表的各个位置,减少哈希冲突的发生概率。
  3. 高效性:计算过程应尽量简洁快速,避免因复杂计算影响哈希表整体性能。

常见的哈希函数实现方式有直接定址法、数字分析法、折叠法等。以简单的整数键为例,可使用取模运算作为哈希函数:index = key % table_size,其中table_size为哈希表的大小,通过此运算将键映射到哈希表的有效索引范围内。

(二)哈希冲突处理

尽管哈希函数追求均匀分布,但由于键的数量可能远超哈希表的存储容量,哈希冲突(即不同键映射到同一存储位置)难以完全避免。目前,解决哈希冲突的方法主要有两类:

  1. 开放地址法:当发生冲突时,按照某种规则在哈希表中寻找下一个可用位置。常见的寻找策略包括线性探测(每次冲突后顺序查找下一个位置)、二次探测(通过二次函数计算下一个位置)等。
  2. 链地址法:哈希表的每个存储位置不再存储单个元素,而是维护一个链表(或其他数据结构)。当冲突发生时,将冲突元素插入到对应位置的链表中。这种方式在处理大量冲突时具有较好的扩展性。

(三)存储结构

哈希表的存储结构通常基于数组实现,数组的每个元素作为哈希表的一个 “桶”(bucket)。在开放地址法中,“桶” 直接存储键值对;在链地址法中,“桶” 存储指向链表头节点的指针,链表中存储实际的键值对。通过哈希函数计算得到的索引,可快速定位到对应的 “桶”,进而进行数据操作。

三、哈希表实现步骤规划

实现哈希表需要遵循严谨的步骤,确保功能的完整性与性能的可靠性。

  1. 定义哈希表结构:确定哈希表的基本组成部分,包括存储数组、哈希表大小、元素类型等,并设计合适的数据结构来承载这些信息。
  2. 实现哈希函数:根据实际应用场景,选择或设计合适的哈希函数,并进行代码实现与测试。
  3. 冲突处理机制实现:依据需求选择开放地址法或链地址法,实现相应的冲突处理逻辑,保证哈希表在冲突情况下仍能正常工作。
  4. 操作接口实现:完成插入、查找、删除等核心操作接口的实现,确保各操作逻辑正确且高效。
  5. 测试:编写全面的测试用例,验证哈希表功能的正确性。

四、代码实现

使用std::vector构造哈希表以提升其查找效率,std::list构建列表,哈希表的增删改查操作流程都是先计算键值对应索引,通过索引取出链表,再对链表进行搜索,具体看代码注释。

#include <algorithm>
#include <iostream>
#include <list>
#include <vector>
#include <string>

template<typename Key, typename Value, typename Hash = std::hash<Key>>
class MyHashTable
{
private:
    class Node
    {
    public:
        Key key;
        Value value;
        //显式构造
        explicit Node(const Key &key) : key(key), value() {}

        Node(const Key &key, const Value &value) : key(key), value(value) {}
        //重载比较操作,按照key比较,为后边对list<Node>进行std::find时提供标准
        bool operator==(const Node &other) const { return key == other.key; }

        bool operator!=(const Node &other) const { return key != other.key; }

        bool operator<(const Node &other) const { return key < other.key; }

        bool operator>(const Node &other) const { return key > other.key; }

        bool operator==(const Key &key_) const { return key == key_; }
    };
    //用vector存储list,此时vector是哈希表,内部的list是应对哈希冲突采用的链表,每个被映射为同一个哈希值的键值对通过链表连接
    std::vector<std::list<Node>> lists_;
    Hash hashfunc_;
    size_t vecsize_;//哈希表大小(索引个数)
    size_t elementsize_;//元素总数

    float load_factor_ = 0.75;//负载因子,元素总数大于等于哈希表大小*负载因子时对哈希表进行扩容并重哈希

    size_t hash(const Key &key) const {return hashfunc_(key) % vecsize_;}
    //重哈希函数,遍历原始哈希表内部每一个元素,计算每一个元素新的哈希表索引并加入到新哈希表内
    void rehash(size_t size)
    {
        std::vector<std::list<Node>> newlists(size);//注意申请新空间是一定要初始化大小,否则插入时越界
        for(std::list<Node> &list : lists_)
        {
            for(Node &node : list)
            {
                size_t newidx = hashfunc_(node.key) % size;
                newlists[newidx].push_back(node);
            } 
        }
        lists_ = std::move(newlists);
        vecsize_ = size;
    }

public:
    MyHashTable(size_t size = 0, const Hash &hashFunc = Hash()): lists_(size), hashfunc_(hashFunc), vecsize_(size), elementsize_(0) {}
    ~MyHashTable()
    {
        clear();
    }

    void insert(const Key &key, const Value &value)
    {
        //向vector一样,再插入时先判断是否需要扩容
        if(elementsize_ + 1 > load_factor_ * vecsize_)
        {
            if(vecsize_ == 0) vecsize_ = 1;
            rehash(vecsize_ * 2);
        }
        //计算新元素哈希索引
        size_t idx = hash(key);
        //找到哈希索引对应链表并查找新元素键值在列表中是否存在,若存在丢弃新元素,只有在不存在时新元素才会插入哈希表
        std::list<Node> &list = lists_[idx];
        if(std::find(list.begin(),list.end(), key) == list.end())
        {
            list.push_back(Node(key, value));
            elementsize_ ++ ;
        }
    }

    void insert(const Key &key) { insert(key, Value{}); }
    //删除元素
    void erase(const Key &key)
    {
        size_t idx = hash(key);
        std::list<Node> &list = lists_[idx];
        auto it = std::find(list.begin(),list.end(), key);
        if(it == list.end())
        {
            return;
        }
        else
        {
            list.erase(it);
            elementsize_ --;
        }
    }

    Value* find(const Key &key)
    {
        size_t idx = hash(key);
        std::list<Node> &list = lists_[idx];
        auto it = std::find(list.begin(), list.end(), key);
        if(it == list.end())
        {
            return nullptr;
        }
        else
        {
            return &it->value;
        }
    }

    size_t size() {return elementsize_;}

    void clear()
    {
        this->lists_.clear();
        this->vecsize_ = 0;
        this->elementsize_ = 0;
    }

};


int main()
{
    MyHashTable<std::string,int> myhash(0);

    myhash.insert("1",1);
    myhash.insert("2",2);
    myhash.insert("3",3);
    myhash.insert("4",4);
    myhash.insert("5",5);
    myhash.insert("6",6);
    myhash.insert("7",7);

    std::cout << *(myhash.find("2")) << std::endl;
    std::cout << *(myhash.find("3")) << std::endl;
    std::cout << *(myhash.find("4")) << std::endl;


    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值