
第三课:《撞车事故现场------哈希冲突》
一、国王的邮箱系统出事故了!
1、上一课里,我们认识了哈希函数。
(1)智慧大臣发明了:
🏆 魔法编号机
规则:
hash = x % 10;
(2)例如:
18 → 8号邮箱
25 → 5号邮箱
42 → 2号邮箱
(3)大家都顺利找到了自己的邮箱。
国王高兴极了:
"这真是世界上最伟大的发明!"
2、可是第二天早上。
邮局管理员慌慌张张跑进皇宫。
大喊:
"不好了!不好了!"
"邮箱撞车了!"
二、什么叫撞车?
1、管理员拿出记录本。
居民:
18
计算:
18 % 10
得到:
8
进入:
8号邮箱
2、又来了一个居民:
28
计算:
28 % 10
得到:
8
竟然也是:
8号邮箱
3、第三位居民:
38
计算:
38 % 10
结果还是:
8号邮箱
4、现在情况变成:
8号邮箱
├── 18
├── 28
└── 38
5、国王傻眼了:
"怎么回事?"
"为什么三个居民抢同一个邮箱?"
三、哈希冲突出现了
这种现象有个专业名字:
哈希冲突(Hash Collision)
1、定义非常简单:
两个不同的数据
经过哈希函数计算
得到同一个位置
就叫:
哈希冲突
2、例如:
18 → 8
28 → 8
38 → 8
虽然:
18 ≠ 28 ≠ 38
但是:
hash(18) = hash(28) = hash(38)
3、这就是:
💥 哈希冲突
四、为什么一定会冲突?
1、有的同学会问:
"汉克老师,能不能设计一个永远不冲突的哈希函数?"
答案:
❌ 基本不可能。
2、我们来看看。
邮箱数量:
10个
居民数量:
100个
3、请问:
100个人
放进
10个邮箱
会发生什么?
肯定有邮箱里不止一个人。
例如:
邮箱1
住10个人
邮箱2
住8个人
邮箱3
住12个人
这就像:
100只小兔子
塞进10个笼子
总会有笼子里挤着好几只兔子。
4、这就是数学里的:
鸽巢原理
(又叫抽屉原理)
5、简单说:
东西比位置多
冲突必然发生
五、哈希表管理员怎么办?
国王问:
"邮箱撞车了怎么办?"
智慧大臣笑了:
"没关系。"
"我们早就准备好了办法。"
于是。
哈希王国发明了两种解决方案。
第一种方案:拉链法
六、拉链法登场
(1)假设:
18 → 8
28 → 8
38 → 8
都进入:
8号邮箱
(2)那怎么办?
大臣说:
"不要赶走他们。"
"让他们排队。"
于是:
8号邮箱
18
↓
28
↓
38
变成:
8号邮箱
|
|--18
|
|--28
|
|--38
(3)就像:
🏠 一个宿舍
里面住了:
18
28
38
三个人。
(4)这种方法叫:
拉链法(Chain)
(5)为什么叫拉链法?
因为像衣服拉链一样:
一个接一个
连起来。
七、查找过程
1、现在找:
28
2、先算:
28 % 10
得到:
8号邮箱
3、进入8号邮箱:
18
28
38
4、依次检查:
18
不是
28
找到
成功!
八、拉链法的优点
1、优点:
✅ 简单
✅ 好理解
✅ 实际应用广泛
2、C++很多哈希表实现都用了类似思想。
第二种方案:开放寻址法
九、停车场故事
1、智慧大臣又发明另一种办法。
(1)假设:
18
进入:
8号邮箱
(2)现在:
28
也想进入:
8号邮箱
(3)结果发现:
8号邮箱满了
怎么办?
2、大臣说:
"向后找空位!"
(1)例如:
邮箱编号
0
1
2
3
4
5
6
7
8 ←18
9 ←空
(2)于是:
28
放到:
9号邮箱
(3)再来:
38
计算:
8号邮箱
发现:
8满了
9满了
继续找。
找到:
0号邮箱
于是:
38
放进去。
(4)结果:
0 ←38
8 ←18
9 ←28
3、这种方法叫:
开放寻址法
(Open Addressing)
意思就是:
原来的位置满了
继续找空位置
十、生活中的理解
1、拉链法像什么?
电影院。
第8排坐满了。
于是:
18
28
38
都坐在同一排。
2、开放寻址法像什么?
停车场。
车位8满了。
那就:
停9
停0
停1
......
一直找空车位。
十一、为什么冲突越少越好?
1、假设:
8号邮箱
18
28
38
48
58
68
78
88
98
2、现在找:
98
必须看:
18
28
38
48
58
68
78
88
98
才能找到。
3、原本:
O(1)
的查找。
慢慢变成:
O(n)
了。
4、所以优秀哈希函数的目标是:
让数据尽量均匀分散
不要全挤在一起。
十二、现实中的哈希表
1、实际C++中的:
unordered_map
内部已经帮我们做好了:
✅ 哈希函数
✅ 冲突处理
✅ 自动扩容
2、例如:
unordered_map<int,int> mp;
mp[18] = 1;
mp[28] = 2;
mp[38] = 3;
即使发生冲突。
程序员也不用管。
系统自动处理。
十三、小试牛刀
规则:
hash = x % 10;
计算下面数字会进入哪个邮箱?
第一题
15
计算:
15 % 10 = 5
答案:
5号邮箱
第二题
25
计算:
25 % 10 = 5
答案:
5号邮箱
发生了什么?
答案:
💥 哈希冲突
第三题
35
计算:
35 % 10 = 5
答案:
5号邮箱
结果:
5号邮箱
15
25
35
又发生冲突了。
本课总结
1、今天我们认识了哈希表最大的敌人:
💥 哈希冲突(Hash Collision)
2、什么是冲突?
不同数据
得到相同邮箱
例如:
18 → 8
28 → 8
38 → 8
3、为什么会冲突?
数据太多
邮箱太少
根据抽屉原理:
冲突不可避免
4、解决办法:
方法1:拉链法
8号邮箱
18
↓
28
↓
38
方法2:开放寻址法
8号邮箱满了
去9号
9号满了
去0号
5、魔法口诀
哈希表,很神奇,
快速查找效率高。
不同数据同位置,
这就叫做哈希冲突。
拉链法,排队站;
开放寻址找空房。
冲突越少越优秀,
查找速度才够强!
下一课,我们将正式进入 C++ 的真实哈希表世界:
《藏宝图仓库------认识 unordered_map》
届时同学们将第一次使用真正的:
unordered_map<string,int>
实现姓名→分数、学号→学生等超级实用的哈希表应用!🚀