哈希函数
哈希函数(y = f(x))的性质:
- 输入域无穷尽,如字符串可以无限长,输出域有穷尽
- 相同的输入,相同的输出,没有随机的成分
- 不同的输入也可能有相同的输出,称作哈希碰撞,但是概率极低,可以等于雷劈的概率
- 哈希函数的输出结果具有均匀性,离散型,比如即使输入有规律,哈希函数也可以使输出是离散的,不规律的,且输出的结果均匀的填充在输出域上。如果你将输出域的各个值%m,那么%后结果也会在0~m-1上均匀分布
相关应用-存数:
假设你只有1G的内存,要判断出0~2^32 -1个数,大约40亿个,其中出现最多次数的数是哪一个
想法一
用哈希表(HashMap),记录下各个数的值和对应出现的次数
- 分析,每一个key(int)和对应的value(int)至少是4 * 2 = 8字节(没有算索引等空间),那么在最差情况需要3.2G的空间,所以不符合要求。
- 1千兆字节(G)=1073741824 字节(B)---约等于10亿字节
想法二
- 利用哈希函数可以得到40亿个哈希值,然后对每一个哈希值%100,那么由于哈希函数的性质,在0~99号文件中含有不同数的数量是差不多一样多的,这样每一个小文件的内存是0.32G,每一个文件取出来最大的那个值后释放,最后比较在这100个中谁最大就OK。
- 流程如下:
- 循环每一个数,依次带入哈希函数得到哈希值
- 先将值%100后值为0的数存入哈希表中,key是这个数字,value是出现的次数
- 循环完成,得到0号文件的最大次数的值,释放0号文件的空间
- 回到第一步重新开始,执行到第2步的时候,这时将%100后的值为 0 + 1 = 1的存入哈希表中。。。(依次进行下去)
哈希表的实现
- 哈希表的增删改查都是O(1),但是不小,理论上是单次是O(logN):因为我们以最差每个链上只可以串2个来看,串完两个就要扩容成两个链,依旧一个链只能串两个,这是所有的N都需要重新计算哈希值,所以一共需要O(N* logN)时间复杂度,其中N是数的个数,logN是扩容的次数。而对于所有的N来说,单个操作时间复杂度是O(logN),因为实际中一个链会有更长的串,所以是O(log以k长为底N)接近O(1),对于java还可以在不用的在线时间的时候自己进行扩容,更加节省时间。
- 存入链的过程是,一个数通过哈希函数计算哈希值,哈希值%(初始数组的长度)即知道放的位置。同样找一个值是否存在的时候也是一样的操作。所以很类似与寻址的过程,时间复杂度主要取决于链的长度和扩容次数。
- 首先要简单介绍一下HashMap的内部存储。我们知道,Map是用来存储key-value类型数据的,一个对在Map的接口定义中被定义为Entry,HashMap内部实现了Entry接口。HashMap内部维护一个Entry数组。当put一个新元素的时候,根据key的hash值计算出对应的数组下标。数组的每个元素是一个链表的头指针,用来存储具有相同下标的Entry。
哈希表的相关操作
package class01;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
public class Code01_HashMap {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
map.put("zuo", "31");
System.out.println(map.containsKey("zuo"));//true
System.out.println(map.containsKey("chengyun"));//false
System.out.println("=========================");
System.out.println(map.get("zuo"));//31
System.out.println(map.get("chengyun"));//null
System.out.println("=========================");
System.out.println(map.isEmpty());//false
System.out.println(map.size());//1
System.out.println("=========================");
System.out.println(map.remove("zuo"));//这个挺有意思,返回的是对应的值31
System.out.println(map.containsKey("zuo"));//false
System.out.println(map.get("zuo"));//null
System.out.println(map.isEmpty());//true
System.out.println(map.size());//0
System.out.println("=========================");
map.put("zuo", "31");
System.out.println(map.get("zuo"));//31
map.put("zuo", "32");
System.out.println(map.get("zuo"));//32
System.out.println("=========================");
map.put("zuo", "31");
map.put("cheng", "32");
map.put("yun", "33");
map.put("yun2", "34");
map.put("yun3", "35");
for (String key : map.keySet()) {//得到key的集合
System.out.println(key);//yun zuo yun2 yun3 cheng无顺序
}
System.out.println("=========================");
for (String values : map.values()) {//得到值的集合
System.out.println(values);//33 31 34 35 32和上面对应,原因和它的原理有关,
// 看似没顺序,其实是通过哈希函数算出了哈希值存储的
}
System.out.println("=========================");
map.clear();
map.put("zuo", "31");
map.put("cheng", "32");
map.put("yun", "33");
map.put("yun2", "34");
map.put("yun3", "35");
//Entry是Map中用来保存一个键值对的,而Map实际上就是多个Entry的集合。
// Entry<key,value>和Map<key,value>一样的理解方式就OK了,只能在哈希表中用
for (Entry<String, String> entry : map.entrySet()) {//得到entry数组的集合
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "," + value);
// yun,33 zuo,31 yun2,34 yun3,35 cheng,32无序
}
System.out.println("=========================");
map.clear();
map.put("A", "1");
map.put("B", "2");
map.put("C", "1");
map.put("D", "3");
map.put("E", "1");
map.put("F", "4");
map.put("G", "2");
//Entry是Map中用来保存一个键值对的,而Map实际上就是多个Entry的集合。
// Entry<key,value>和Map<key,value>一样的理解方式就OK了,只能在哈希表中用
for (Entry<String, String> entry : map.entrySet()) {//得到entry数组的集合
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "," + value);
// A,1 B,2 C,1 D,3 E,1 F,4 G,2 还挺有意思是顺序出来的,但是对比上一段代码,觉得这是巧合,只是和哈希函数有关系
}
System.out.println("=========================");
//不能在使用迭代器的同时移除数据
// you can not remove item in map when you use the iterator of map
// for(Entry<String,String> entry : map.entrySet()){
// if(!entry.getValue().equals("1")){
// map.remove(entry.getKey());
// }
// }
//解决办法
// if you want to remove items, collect them first, then remove them by
// this way.
List<String> removeKeys = new ArrayList<String>();
for (Entry<String, String> entry : map.entrySet()) {
if (!entry.getValue().equals("1")) {
removeKeys.add(entry.getKey());
}
}
for (String removeKey : removeKeys) {
map.remove(removeKey);
}
for (Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "," + value);
//A,1 C,1 E,1
}
System.out.println("=========================");
}
}
相关题目-实现结构
【题目】 设计一种结构,在该结构中有如下三个功能: insert(key):将某个key加入到该结构,做到不重复加入 delete(key):将原本在结构中的某个key移除 getRandom(): 等概率随机返回结构中的任何一个key。
【要求】 Insert、delete和getRandom方法的时间复杂度都是O(1)
public class test1{
public static class Pool<K>{
//之所以要建立两个Map是因为,删除操作给的参数是对应的要删除的数,不是索引
private HashMap<K, Integer> keyIndexMap;
private HashMap<Integer, K> indexKeyMap;
private int size;
public Pool(){
this.keyIndexMap = new HashMap<K, Integer>();
this.indexKeyMap = new HashMap<Integer, K>();
this.size = 0;
}
public void insert(K key){
if (!this.keyIndexMap.containsKey(key)){
this.keyIndexMap.put(key, this.size);
this.indexKeyMap.put(this.size++, key);
}
}
public void delete(K key){
if (this.keyIndexMap.containsKey(key));
//得到要删除键的索引数
int deleteIndex = this.keyIndexMap.get(key);
int lastIndex = --this.size;
//得到最后一个位置的索引数对应的键
K lastKey = this.indexKeyMap.get(lastIndex);
//修改操作,使最后一个位置的键和新的索引数建立对应
this.keyIndexMap.put(lastKey, deleteIndex);
this.indexKeyMap.put(deleteIndex, lastKey);
//移除删除的键
this.keyIndexMap.remove(key);
//在另一个Map中移除最后的索引键值对
this.indexKeyMap.remove(lastIndex);
}
public K getRandom(){
if (this.size == 0){
return null;
}
int randomIndex = (int) (Math.random() * this.size);
return this.indexKeyMap.get(randomIndex);
}
}
public static void main(String[] args){
Pool<String> pool = new Pool<>();
pool.insert("zuo");
pool.insert("cheng");
pool.insert("yun");
System.out.println(pool.getRandom());
System.out.println(pool.getRandom());
System.out.println(pool.getRandom());
System.out.println(pool.getRandom());
}
}