什么是哈希表?
哈希表(Hash Table)是一种非常实用的数据结构,就像我们生活中的字典或电话簿一样,可以帮助我们快速找到需要的信息。
生活中的例子
想象一下,你有很多本书,想快速找到其中一本。如果把书随便堆放,找起来会很慢。但如果给每本书贴上个编号,并按编号顺序排列,就能很快找到。
哈希表就用了类似的思想:给每个数据分配一个"编号"(哈希值),然后按照这个编号存储和查找数据。
关键点
- 哈希表能让我们快速查找、添加和删除数据
- 核心是"哈希函数",它能把数据转换成一个数字(哈希值)
- 通过哈希值可以直接定位数据的存储位置,不需要逐个查找
哈希表的工作原理
计算哈希值
当我们要存储数据时,首先通过"哈希函数"计算这个数据的哈希值。
例如:计算姓名"张三"的哈希值,可以把每个字的ASCII码相加,再取一个合适的数字的余数。
存储数据
根据计算出的哈希值,把数据存到哈希表中对应的位置。
哈希表可以想象成一个数组,每个位置都有编号,哈希值就是我们要存储的位置编号。
查找数据
当需要查找数据时,再次计算它的哈希值,然后直接到对应位置查看即可。
这就是哈希表快的原因 - 不需要从头开始找,直接定位!
哈希冲突及解决方法
有时候,不同的数据可能会计算出相同的哈希值,这就是"哈希冲突"。比如"张三"和"张三丰"可能得到相同的哈希值。
线性探测法
如果计算的位置已被占用,就检查下一个位置(index+1),直到找到空位置。
链地址法
每个位置不只存一个数据,而是存一个链表。有冲突时,就把新数据加到链表后面。
优点
- 查找速度快,平均时间复杂度为O(1)
- 添加和删除数据也很高效
- 使用灵活,适用于很多场景
缺点
- 需要处理哈希冲突
- 哈希函数设计不好会影响性能
- 占用空间相对较大
- 数据没有顺序,不能按顺序遍历
C++哈希表示例代码
#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. 查找数据
计算要查找的姓名的哈希值,直接到对应位置查找,不需要遍历整个数组。
练习题
简单的用户名密码存储
问题描述:实现一个哈希表,用于存储用户名和对应的密码。要求能实现添加新用户和查询密码两个功能。
提示:
- 用结构体存储用户名和密码(
struct User { string name; string pwd; bool exists; };
) - 哈希函数可以简单计算用户名每个字符的ASCII值之和,再对哈希表大小取余
- 哈希表大小可以固定为20(
const int SIZE = 20;
)
示例:
查询:输入"小红",输出"abcdef"
单词出现次数统计
问题描述:给定一段简单的英文句子(比如"I like like programming"),用哈希表统计每个单词出现的次数。
提示:
- 结构体可以设计为
struct WordCount { string word; int count; bool exists; };
- 哈希函数可以用单词长度乘以首字母ASCII值,再取余
- 步骤:先分割句子得到单个单词,再逐个插入哈希表(若已存在则次数+1)
示例:
输出:apple:3, banana:2, orange:1
处理简单的哈希冲突
问题描述:在练习题1的基础上,当两个不同的用户名计算出相同的哈希值(哈希冲突)时,用"线性探测法"解决(即往后找第一个空位置存储)。
提示:
- 哈希冲突举例:"张三"和"张三丰"可能算出相同的哈希值
- 插入时,如果计算的位置已被占用,就检查下一个位置(index+1),直到找到空位置
- 查找时,先按哈希值找,若不匹配则继续检查下一个位置
简易电话簿
问题描述:实现一个电话簿哈希表,支持添加联系人、查找电话号码和删除指定联系人三个功能。
提示:
- 删除功能可以通过标记
exists = false
实现 - 哈希函数可以自定义(比如姓名长度+最后一个字符的ASCII值)
哈希表的应用场景
用户信息存储
网站和应用程序用哈希表存储用户账号、密码等信息,实现快速登录验证。
缓存系统
浏览器和服务器用哈希表实现缓存,快速存储和获取常用数据,提高访问速度。
电话簿
手机中的电话簿就是用类似哈希表的结构,让我们能快速查找联系人。
数据统计
统计单词出现次数、投票结果等场景,哈希表能高效记录和更新数据。
字典查询
电子词典应用用哈希表存储单词和解释,实现快速查询功能。
购物车
电商网站的购物车功能常用哈希表实现,方便添加、删除和修改商品。