哈希表入门教程

简单易懂的哈希表知识,连初中生都能轻松掌握

什么是哈希表?

哈希表(Hash Table)是一种非常实用的数据结构,就像我们生活中的字典或电话簿一样,可以帮助我们快速找到需要的信息。

生活中的例子

想象一下,你有很多本书,想快速找到其中一本。如果把书随便堆放,找起来会很慢。但如果给每本书贴上个编号,并按编号顺序排列,就能很快找到。

哈希表就用了类似的思想:给每个数据分配一个"编号"(哈希值),然后按照这个编号存储和查找数据。

电话簿示例图,展示按某种规则排列的联系人

关键点

  • 哈希表能让我们快速查找、添加和删除数据
  • 核心是"哈希函数",它能把数据转换成一个数字(哈希值)
  • 通过哈希值可以直接定位数据的存储位置,不需要逐个查找

哈希表的工作原理

1

计算哈希值

当我们要存储数据时,首先通过"哈希函数"计算这个数据的哈希值。

例如:计算姓名"张三"的哈希值,可以把每个字的ASCII码相加,再取一个合适的数字的余数。

2

存储数据

根据计算出的哈希值,把数据存到哈希表中对应的位置。

哈希表可以想象成一个数组,每个位置都有编号,哈希值就是我们要存储的位置编号。

3

查找数据

当需要查找数据时,再次计算它的哈希值,然后直接到对应位置查看即可。

这就是哈希表快的原因 - 不需要从头开始找,直接定位!

哈希冲突及解决方法

有时候,不同的数据可能会计算出相同的哈希值,这就是"哈希冲突"。比如"张三"和"张三丰"可能得到相同的哈希值。

线性探测法

如果计算的位置已被占用,就检查下一个位置(index+1),直到找到空位置。

链地址法

每个位置不只存一个数据,而是存一个链表。有冲突时,就把新数据加到链表后面。

优点

  • 查找速度快,平均时间复杂度为O(1)
  • 添加和删除数据也很高效
  • 使用灵活,适用于很多场景

缺点

  • 需要处理哈希冲突
  • 哈希函数设计不好会影响性能
  • 占用空间相对较大
  • 数据没有顺序,不能按顺序遍历

C++哈希表示例代码

hash_table_example.cpp
#include <iostream>
#include <string>
using namespace std;

// 哈希表的大小
const int TABLE_SIZE = 10;

// 学生结构体:存储姓名和成绩
struct Student {
    string name;  // 学生姓名
    int score;    // 学生成绩
    bool exists;  // 标记这个位置是否有数据
};

// 哈希函数:计算姓名的哈希值
int hashFunction(string name) {
    int sum = 0;
    // 把姓名中每个字符的ASCII值加起来
    for (int i = 0; i < name.length(); i++) {
        sum += name[i];
    }
    // 取余数,确保哈希值在数组范围内
    return sum % TABLE_SIZE;
}

int main() {
    // 创建哈希表(数组)
    Student hashTable[TABLE_SIZE];
    
    // 初始化哈希表
    for (int i = 0; i < TABLE_SIZE; i++) {
        hashTable[i].exists = false;
    }
    
    // 向哈希表中添加数据
    string names[] = {"张三", "李四", "王五", "赵六"};
    int scores[] = {90, 85, 95, 88};
    
    for (int i = 0; i < 4; i++) {
        int index = hashFunction(names[i]);
        hashTable[index].name = names[i];
        hashTable[index].score = scores[i];
        hashTable[index].exists = true;
        cout << names[i] << " 的位置是 " << index << endl;
    }
    
    // 从哈希表中查找数据
    string searchName = "李四";
    int searchIndex = hashFunction(searchName);
    
    if (hashTable[searchIndex].exists && hashTable[searchIndex].name == searchName) {
        cout << endl << searchName << " 的成绩是: " << hashTable[searchIndex].score << endl;
    } else {
        cout << endl << "没有找到 " << searchName << endl;
    }
    
    return 0;
}

代码解释

1. 结构体定义

我们定义了一个Student结构体,用来存储学生的姓名、成绩和一个存在标记。

2. 哈希函数

hashFunction函数把学生姓名转换成一个数字(哈希值),方法是把每个字符的ASCII值相加,再对哈希表大小取余。

3. 初始化哈希表

我们用一个数组来实现哈希表,并初始化所有位置为"不存在"状态。

4. 添加数据

计算每个学生姓名的哈希值,然后把学生信息存到对应位置。

5. 查找数据

计算要查找的姓名的哈希值,直接到对应位置查找,不需要遍历整个数组。

练习题

1

简单的用户名密码存储

问题描述:实现一个哈希表,用于存储用户名和对应的密码。要求能实现添加新用户和查询密码两个功能。

提示:

  • 用结构体存储用户名和密码(struct User { string name; string pwd; bool exists; };
  • 哈希函数可以简单计算用户名每个字符的ASCII值之和,再对哈希表大小取余
  • 哈希表大小可以固定为20(const int SIZE = 20;

示例:

添加用户:("小明", "123456")、("小红", "abcdef")
查询:输入"小红",输出"abcdef"
2

单词出现次数统计

问题描述:给定一段简单的英文句子(比如"I like like programming"),用哈希表统计每个单词出现的次数。

提示:

  • 结构体可以设计为struct WordCount { string word; int count; bool exists; };
  • 哈希函数可以用单词长度乘以首字母ASCII值,再取余
  • 步骤:先分割句子得到单个单词,再逐个插入哈希表(若已存在则次数+1)

示例:

输入句子:"apple banana apple orange banana apple"
输出:apple:3, banana:2, orange:1
3

处理简单的哈希冲突

问题描述:在练习题1的基础上,当两个不同的用户名计算出相同的哈希值(哈希冲突)时,用"线性探测法"解决(即往后找第一个空位置存储)。

提示:

  • 哈希冲突举例:"张三"和"张三丰"可能算出相同的哈希值
  • 插入时,如果计算的位置已被占用,就检查下一个位置(index+1),直到找到空位置
  • 查找时,先按哈希值找,若不匹配则继续检查下一个位置
4

简易电话簿

问题描述:实现一个电话簿哈希表,支持添加联系人、查找电话号码和删除指定联系人三个功能。

提示:

  • 删除功能可以通过标记exists = false实现
  • 哈希函数可以自定义(比如姓名长度+最后一个字符的ASCII值)

哈希表的应用场景

用户信息存储

网站和应用程序用哈希表存储用户账号、密码等信息,实现快速登录验证。

缓存系统

浏览器和服务器用哈希表实现缓存,快速存储和获取常用数据,提高访问速度。

电话簿

手机中的电话簿就是用类似哈希表的结构,让我们能快速查找联系人。

数据统计

统计单词出现次数、投票结果等场景,哈希表能高效记录和更新数据。

字典查询

电子词典应用用哈希表存储单词和解释,实现快速查询功能。

购物车

电商网站的购物车功能常用哈希表实现,方便添加、删除和修改商品。