哈希表

哈希表是根据关键码的值而直接进行访问的数据结构

那么哈希表能解决什么问题呢,一般哈希表都是用来快速判断一个元素是否出现集合里。
但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。

哈希函数

image.png
如果hashCode得到的数值大于 哈希表的大小了,也就是大于tableSize了,怎么办呢?
此时为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,就要我们就保证了学生姓名一定可以映射到哈希表上了。

哈希碰撞

如图所示,小李和小王都映射到了索引下标 1 的位置,这一现象叫做哈希碰撞
image.png
两种解法

拉链法

image.png
(数据规模是dataSize, 哈希表的大小为tableSize)
其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。

线性探测法

使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放 冲突的数据了。如图所示:
image.png

三种哈希结构

  • 数组
  • set (集合)
  • map(映射) | 集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 | | —- | —- | —- | —- | —- | —- | —- | | std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) | | std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) | | std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |

std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。

映射 底层实现 是否有序 数值是否可以重复 能否更改数值 查询效率 增删效率
std::map 红黑树 key有序 key不可重复 key不可修改 O(logn) O(logn)
std::multimap 红黑树 key有序 key可重复 key不可修改 O(log n) O(log n)
std::unordered_map 哈希表 key无序 key不可重复 key不可修改 O(1) O(1)

std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。

当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。

哈希表运用

数组就是简单的哈希表,但是数组的大小可不是无限开辟的

字符串处理

题目:

242. 有效的字母异位词

数组其实就是一个简单哈希表,而且这道题目中字符串只有小写字符,那么就可以定义一个数组,来记录字符串s里字符出现的次数。
我的解法(空间爆炸):

  1. bool isAnagram(string s, string t) {
  2. int m = s.length();
  3. int n = t.length();
  4. multiset<char> a, b;
  5. int i ;
  6. for (i = 0; i < m; i++)
  7. a.insert(s[i]);
  8. for (i = 0; i < n; i++)
  9. b.insert(t[i]);
  10. if (a.size() != b.size())
  11. return false;
  12. else
  13. return a==b;
  14. return true;
  15. }

空间爆炸2:

  1. bool isAnagram(string s, string t) {
  2. vector<char> vec1,vec2;
  3. vec1.assign(s.begin(), s.end());
  4. vec2.assign(t.begin(), t.end());
  5. sort(vec1.begin(), vec1.end());
  6. sort(vec2.begin(), vec2.end());
  7. return vec1 == vec2;
  8. }

383. 赎金信

创建字母数组比较

49. 字母异位词分组

我的解法:
https://github.com/kaixuhuang/CPP/blob/master/hash/hash.cpp

数组交集

题目:

349. 两个数组的交集

使用数组来做哈希的题目,是因为题目都限制了数值的大小。
而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费

350. 两个数组的交集 II

快乐数

202. 快乐数

两数之和

1. 两数之和

四数相加

454. 四数相加 II

将4重遍历转化为两两遍历 ,将时间复杂度从哈希表 - 图5减到哈希表 - 图6,大大提高运算效率

赎金信

383. 赎金信

三数之和

15. 三数之和

我的解答: