哈希值运算
- 除留余数法:用关键字k除以某个不大于哈希表长度m的数p,将所得余数作为哈希表地址。
index = hashCode % n;
- 位运算法:运用位运算,注意这里要保证 length=2 ^n。
int index= h & (length-1);
//例如:15%4=3 ==>15&3=1111&0011=0011=3
原理:从二进制角度来看,X/2 ^n 相当于X>>n,就是把X后移n位,被移掉的部分,则是 X % 2 ^n,所以X%2 ^n就是要获取后n位。
为了保证数组大小是2 ^n,计算超过 capacity 的最小 2 ^n:
int getSize(int capacity) {
int size = 1;
while (size < capacity) {
size <<= 1;
}
return size;
}
这种方法的好处是效率比普通计算高,而且可以使结果均匀散裂,减少哈希碰撞。
解决地址冲突
由于相同索引的值不一定只有一个,所以需要解决地址冲突,其实这个是哈希表的核心问题:
最经典的方式就是链地址法,将相同索引的值,通过拉链法挂在hash[index]后。
保存数据有两种方式:数组和链表。数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。上面提到的链地址法,其实就是将数组和链表组合在一起,发挥了两者的优势,我们可以将其理解为链表的数组。
如果链表长度足够长怎么办?
同时,当链表长度hashmap还有红黑树,为了实现比此时链表更高效的查找。但是红黑树的出现是有要求的,要求链表长度>8,并且数组长度>64。如果数组长度没有超过64则在冲突时首先采用扩容的方法。扩容时,负载因子会确定在它没满之前的负载量,达到就扩容,最大容量是2^30。扩容后要rehash,把哈希值映射到新的哈希值中。哈希值只会于原位置或者是原位置+旧容量(二进制的第一位决定,是1的话则扩容那一位与运算会生效)。
为何阈值是8?
因为每个空间中的节点的数量分布服从泊松分布,8的时候几乎没有,自动转为树(不想让你转为树,因为树节点内存大),是时间与空间的权衡。
如何实现均匀散列?
哈希冲突的原因:高位不同、低位相同,导致散列很多。让低位和高位结合在一起看,使任何一位发生变化都能影响到最后的index值。
HashMap想了一种办法(扰动):将Hash值的高16位右移并与原Hash值取异或运算(^),混合高16位和低16位的值,得到一个更加散列的低16位的Hash值。再通过哈希函数找到它的index值,如:
// 没有Hash碰撞
H1 = H1 ^ (H1 >>> 16) = 5;
H2 = H2 ^ (H2 >>> 16) = 250;
index1 = (2 ^n -1) & H1 = 5 & 15 = 5
index2 = (2 ^n -1) & H2 = 250 & 15 = 10
还有办法嘛?
开放地址法:要是发生冲突就往后一个,直到找到一个空位。
再哈希法:用另外一个哈希函数找到另外一个key值。
建立公共溢区
常考题:Leetcode:前K个高频元素
思路:
定义数据结构:priority_queue,unordered_map,vector;
1.定义hashmap用来存储value和对应的频率,即
unordered_map <pair<int,int>>;
2.按照频率排序,放入优先队列中(即堆排序);
3.取优先队列中,频率前k个的对应值;
代码:
vector<int> topKFrequent(vector<int>& nums, int k) {
if(nums.empty()){return {};}
unordered_map<int,int> map; //定义hashmap(容器)
for(auto i:nums){
map[i]++;
}
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> que; //定义一个小顶堆(优先队列默认是大顶堆)
/*
struct cmp{
bool const operator()(pair<int,int> rn,pair<int,int> ln){ // 从小到大,小心写反!!
if(ln.first<rn.first). // 从小到大,小心写反!!
return true;
return false; }
}; */
//priority_queue<pair<int,int>,vector<pair<int,int>>,cmp; //如果需要自定义排序函数
vector<int> res;
for(auto it=map.begin();it!=map.end();it++)
{
que.push(make_pair(it->second,it->first));
if(int(que.size())>k) //维护一个大小为k的顶堆,节省时间
{
que.pop();
}
}
while(que.size()>0)
{
res.push_back(que.top().second);
que.pop();
}
return vector<int>(res.rbegin(),res.rend());//由于是小顶堆,结果应为逆序
}
时间复杂度:
- 存储hashmap,O(n);
- 遍历hashmap的数据进行堆排序:遍历N个数*维护一个大小为k的小顶堆O(nlogk);
- 转化为大小为k的结果:O(k);
- 总共:O(nlogk)