手把手教你实现哈希表:从代码到原理的新手友好指南

贾蔷
• 阅读 5

一、简介和应用 哈希表(Hash Table)是一种高效的数据结构,通过哈希函数将键(Key)映射到存储位置,实现O(1)时间复杂度的查找、插入和删除操作。它广泛应用于缓存系统、数据库索引、字典查询等场景。例如,在编程中需要快速根据用户ID查找信息时,哈希表能显著提升效率。本文将通过手写的C++哈希表代码,带您从零开始理解其实现原理。

二、特点和注意事项

  1. 特点:

    快速访问:通过哈希函数直接定位数据,避免线性查找。

    动态扩容:本例中默认大小为1000,可自定义大小(但需注意扩容策略未在代码中实现)。

    冲突解决:使用链地址法(即每个哈希桶存储链表),解决不同键映射到同一地址的问题。

  2. 注意事项:

    哈希函数设计:需尽量均匀分布,减少冲突(本例用取模运算,简单但可能不均)。

    内存管理:代码中使用了new动态分配节点,但未显式释放,实际应用需考虑内存泄漏风险。

    链表长度:若冲突过多,链表过长可能导致性能下降,需优化哈希函数或改用其他策略。

三、实现步骤

  1. 定义数据结构:

    使用pairs结构体存储键值对(key和val)。

    listnode节点包含值及指向下一个节点的指针,形成链表。

    hash_map类维护哈希表数组(存储链表头指针)和表大小。

  2. 构造函数:

    默认或传入指定大小初始化数组,并全部置空。

  3. 核心方法:

    ins():计算键的哈希地址,新建节点插入链表末尾(处理头节点为空和冲突情况)。

    del():定位键的哈希地址,遍历链表删除对应节点(需判断头节点是否为目标)。

    find():查找并返回指定键的值,未找到则返回-1。

    print():遍历整个哈希表输出所有键值对。

  4. 冲突处理:通过链表串联同一地址下的多个元素,避免数据覆盖。

四、代码和注释 C++ #include using namespace std;

// 键值对结构体 struct pairs { int key; // 键 int val; // 值 };

// 链表节点(存储键值对) template struct listnode { T val; // 节点值 listnode* next = nullptr; // 指向下一个节点

// 构造节点
listnode() {}                // 空构造
listnode(T v) { val = v; }    // 传入值构造

};

// 哈希表类 class hash_map { private: listnode** map; // 存储链表头指针的数组 int size; // 哈希表大小

public: // 默认构造函数(初始化大小为1000) hash_map() { size = 1000; map = new listnode*[size]; // 动态分配数组 for (int i = 0; i < size; i++) { // 初始化所有位置为空 map[i] = nullptr; } }

// 指定大小构造函数
hash_map(int size) {
    this->size = size;                // 使用传入的大小
    map = new listnode<pairs>*[size];
    for (int i = 0; i < size; i++) {
        map[i] = nullptr;
    }
}

// 插入键值对
void ins(pairs pair) {
    int address = pair.key % size;     // 哈希函数:取模定位地址
    listnode<pairs>* newnode = new listnode<pairs>(pair); // 创建新节点

    listnode<pairs>* tmp = map[address];  // 当前地址的链表头
    if (!tmp) {                          // 若头节点为空,直接插入
        map[address] = newnode;
        return;
    }
    while (tmp->next) {                  // 否则遍历到链表末尾
        tmp = tmp->next;
    }
    tmp->next = newnode;                 // 插入末尾
}

// 删除指定键
void del(int key) {
    int address = key % size;            // 定位地址
    listnode<pairs>* tmp = map[address];
    if (tmp->val.key == key) {           // 若头节点就是目标
        map[address] = map[address]->next;  // 删除头节点
        return;
    }
    while (tmp->next->val.key!= key) {  // 遍历查找目标节点
        tmp = tmp->next;
    }
    tmp->next = tmp->next->next;        // 删除目标节点
}

// 查找指定键的值
int find(int key) {
    int address = key % size;
    listnode<pairs>* tmp = map[address];
    while (tmp && tmp->val.key!= key) {  // 遍历查找
        tmp = tmp->next;
    }
    if (!tmp) return -1;                 // 未找到返回-1
    return tmp->val.val;                  // 返回值
}

// 打印所有键值对
void print() {
    for (int i = 0; i < size; i++) {
        listnode<pairs>* tmp = map[i];
        while (tmp) {                    // 遍历当前地址的链表
            cout << tmp->val.key << ":" << tmp->val.val << " ";
            tmp = tmp->next;
        }
        cout << endl;                    // 换行分隔不同地址
    }
    cout << endl;
}

}; 五、总结 通过本文的代码与注释,您已初步掌握了哈希表的核心实现逻辑:利用哈希函数映射键到地址,通过链表解决冲突,从而实现高效的增删查操作。实际应用中,需进一步优化哈希函数(如使用更均匀的算法)和考虑内存管理。建议新手从简单示例入手,逐步实践,理解数据结构背后的设计思想,为后续学习更复杂算法打下基础。 来源:手把手教你实现哈希表:从代码到原理的新手友好指南

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
15.链地址法
同样是为了解决哈希表中索引重复问题的算法,基本思路为将哈希表中维护的数组改成存储链表的数组,将数据存在链表中。也可以用数组但是数组的插入和删除的效率较低,故采用链表。实现:链表的实现:/链结点,相当于是车厢/publicclassNode{//数据域publi
Stella981 Stella981
3年前
Nginx数据结构之散列表
1\.散列表(即哈希表概念)散列表是根据元素的关键码值而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找速度。这个映射函数f叫做散列方法,存放记录的数组叫做散列表。若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上。由此,不需要比较便可直接取得所
Wesley13 Wesley13
3年前
MySql数据库索引
InnoDB存储引擎索引:B树索引:不能找到一个给定键值的具体行,能找到的只是被查找数据行所在的页。然后把页加载到内存,在查询所要的数据。全文索引:哈希索引:InnoDB会根据表的使用情况自动为表生成哈希索引,不能人为的干预是否在一张表中生成哈希索引B树索引在数据库中的高度一般是2~4层,所以查询最多需要2到4次IO。B树索引分为聚
Stella981 Stella981
3年前
Consistent hashing一致性算法原理
最近在整理redis分布式集群,首先就整理一下分布式算法原理。常见的分区规则有哈希分区和顺序分区两种,Redis采用的是哈希分区规则。节点取余分区使用特定的数据,如Redis的键或用户ID为key,节点数量为N,则:hash(key)%N,计算出哈希值,然后决定映射到哪个节点上,如节点数为4时,哈希值的结果可能为0、1、2,3.现假
Wesley13 Wesley13
3年前
Java集合之Map接口
Map使用键值对来存储数据,将键映射到值对象,一个映射不能包含重复的键,每一个键最多只能映射到一个值。Map接口的具体实现类:HashMap,Hashtable,TreeMap,LinkedHashMap  1)HashMap  基于哈希表(哈希表学习地址)的Map接口实现。允许使用null值和null键,不保证映射的顺序,特别是不保证顺序恒
Stella981 Stella981
3年前
Redis哈希对象的ziplist编码实现了O(1)复杂度吗
问题:Redis中哈希对象有两种编码方式,分别是ziplist、hashtable方式。哈希对象,总得体现哈希算法,使得基本操作达到O(1)的效率。hashtable编码方式使用字典,也即是Java中hashMap的方式,这个我可以理解。但是,ziplist方式所有元素都是紧挨的,它是怎么实现hash,并使得查询等操作有O(1)的时间效率的呢?让我们
Wesley13 Wesley13
3年前
Java学习系列——第3课Java 高级教程
java数据结构  1)【概述】  Java的工具包提供了强大的数据结构在的Java中的数据结构主要包括以下几种接口和类:枚举(枚举)位集合(位集合)向量(矢量)栈(栈)字典(词典)哈希表(哈希表)属性(属性)
Wesley13 Wesley13
3年前
MySQL索引初探
一、什么是索引?帮助数据库系统实现高效获取数据的数据结构索引可以帮助我们快速地定位到数据而不需要每次搜索的时候都遍历数据库中的每一行。二、常见实现方式有哪些?常见索引模型有三种:哈希表、有序数组、搜索树1.哈希表(1)使用哈希表实现的索引称为哈希索引。!(https://os
小万哥 小万哥
1年前
Java HashMap 和 HashSet 的高效使用技巧
JavaHashMapHashMap是一种哈希表,它存储键值对。键用于查找值,就像数组中的索引一样。HashMap的优势在于它可以使用任何类型作为键,并且查找速度很快。创建HashMapjava//导入HashMap类importjava.util.Has
贾蔷 贾蔷
3天前
哈希表实现指南:从原理到C++实践
一、简介和应用哈希表(HashTable)是一种高效的数据结构,通过键值对(keyvalue)存储数据,提供快速的插入、删除和查找操作。它使用哈希函数将键映射到表中的位置,使得平均时间复杂度可以达到O(1)。‌应用场景‌:数据库索引、缓存实现(如Redis