Day6
哈希表
哈希表实际上就是通过键key
而直接访问值value
的数据结构
哈希表(h)
的表现形式如数组(v)
一般
- 在数组中新增一个元素,我们可以在尾部
push_back
,或者在某个位置insert
- 在哈希表中新增一个元素,需要一个键值对(key-value pair),我们再通过计算
key
来得到数据value
在哈希表中的具体位置,随后再插入。 - 在数组中我们知道可以通过数组的索引来随机访问数组中的数据内容,利用
v[index]
即可 - 在哈希表中我们也可以通过[]来直接访问数据,只不过[]中填入的内容是
key
,例如h[key]
对于数组和哈希表来讲,访问元素的时间都是O(1),因为他们都支持随机访问,即利用[]访问数据
在查询方面,哈希表比数组有优势。当我们要查找是否存在某个元素时,数组需要花O(n)
的时间遍历全部才能得出结论,而哈希表只需要检查h[key]
是否存在在表中就能在O(1)
时间内得出结果
哈希函数
哈希函数是将我们存入的数据映射到哈希表中的函数,通过数学运算给出一个哈希表中的位置,后续我们根据这个位置才能向表中插入数据。即:它能把数据映射成哈希表上的数字索引。
如果hashCode得到的索引大于哈希表的大小了怎么办?
- 为了保证映射出来的索引数值都落在哈希表上,我们会再次对数值做一个取模,这样就保证了学生姓名一定可以映射到哈希表上了。
如果学生的数量大于哈希表的大小怎么办?
- 此时就算哈希函数计算的再均匀,也会有几位学生的名字同时映射到哈希表同一个索引的位置,此时就会发生哈希碰撞。
哈希碰撞
如图,假设小李和小王都映射到了索引下标 1 的位置,这就发生了冲突,即哈希碰撞。
解决碰撞一般有两种
拉链法
我们将冲突的数据存成链表的形式,这样多个数据都可以保留了
使用拉链法要选择适当的哈希表大小,不会因为表太大空值太多而浪费内存,也不会因为表太小全是链表而影响查询速度。(因为链表需要O(n)
查找)
线性探测法
线性探测法要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置存放冲突的数据了。
常见的哈希结构
- 数组
- set (集合)
- map(映射)
对于集合:
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
对于映射:
映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
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) |
当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
总结
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。
242. Valid Anagram
class Solution {
public:
bool isAnagram(string s, string t) {
if(s.length() != t.length())
return false;
unordered_map<char, int> mp;
for(char c : s)
{
++mp[c];
}
for(char c : t)
{
--mp[c];
if(mp[c] < 0)
return false;
}
return true;
}
};
349. Intersection of Two Arrays
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int, int> mp;
for(int i : nums1)
{
++mp[i];
}
vector<int> ans;
for(int i : nums2)
{
if(mp[i] != 0){
ans.push_back(i);
mp[i] = 0;
}
}
return ans;
}
};
202. Happy Number
class Solution {
public:
bool isHappy(int n) {
set<int> s;
int sum = 0;
while(n != 1)
{
int tmp = n;
while(1)
{
int num = tmp % 10;
sum += num * num;
tmp = tmp / 10;
if(tmp == 0)
break;
}
if(s.find(sum) != s.end())
return false;
s.insert(sum);
n = sum;
sum = 0;
}
return true;
}
};
1. Two Sum
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> map;
for(int i=0; i<nums.size(); ++i) {
if(map[nums[i]] != 0) {
return {i, map[nums[i]]-1};
}
map[target-nums[i]] = i+1;
}
return {};
}
};