Hashmap的相关问题

哈希值运算

  • 除留余数法:用关键字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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值