1.散列函数
散列函数:无论你给它什么数据,它都还你一个数字。
散列函数 将输入映射到数字。你可能认为散列函数输出的数字没什么规律,但其实散列函数必须满足一些要求。
1、它必须是一致的。例如,假如你输入apple时得到的是4,那么每次输入apple时,得到的都必须为4.如果不是这样,散列表将毫无用处。
2、它应将不同的输入映射到不同的数字。例如,如果一个散列函数无论输入什么都返回1,它就不是好的散列函数。最理想的情况是,将输入的映射到不同的数字。
散列函数准确的指出了数字的存储位置,你根本不用查找!之所以能够这样,具体原因如下。
1、散列函数总是将同样的输入到相同的索引。每次你输入acocado,得到的都是同一个数字。因此,你可首先使用它来确定将鳄梨的价格存储在什么地方,并在以后使用它来确定鳄梨的价格存储在什么地方。
2、散列函数将不同的输入映射到不同的索引。acocado映射到索引4,milk映射到索引0,每种商品都映射到数组的不同位置,让你能够将其价格存储到这里。
3、散列函数知道数组有多大,只返回有效的索引。如果数组包含5个元素,散列函数就不会返回无效索引100。
散列表也被称为散列映射,映射,字典和关联数组。散列表的速度很快!你可以立即获取数组中的元素,而散列表也是用数组来存储数据,因此获取元素的速度和数组一样快。
散列表 = 哈希表
js 实现哈希表
const m = new Map();
const ma = new Map([
['name', 'zhangsan'],
['age', 10]
]);
ma.size // 2
ma.has('name') // true
ma.get('name') // "张三"
let map = new Map();
map.set(-0, 123);
map.get(+0) // 123
map.set(true, 1);
map.set('true', 2);
map.get(true) // 1
map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3
map.set(NaN, 123);
map.get(NaN) // 123
集合中的键和值可以是任何类型。如果使用现有密钥向集合添加值,则新值会替换旧值。
1. 使用的是 Map,Map 更类似于 HashMap 而不是 Hashset
2. 广义来说,判断 对象是否存在在 HashMap 中的均摊时间复杂度为 O(1),
狭义来说,不同的编程语言实现 HashMap 的方式都不同,但是 Javascript 的 Map 对象非常类似哈希表,所以也是 O(1)
案例
模拟一个电话表 并查询姓名是否重复
let voted = new Map();
voted.set('王二',18866669999)
if(voted.has('王二')){
console.log('已经有了')
}else{
voted.set('王二',18866669999)
}
散列表适用于:
模拟映射关系;
防止重复
缓存/记住数据,以免服务器在通过处理来生成它们。
冲突
散列函数总是将不同的键映射到数组的不同位置。实际上几乎不可能编写出这样的散列函数。
这里的经验教训有两个。
散列函数很重要。前面的散列函数将所有的键都映射到一个位置,而最理想的情况是,算列函数将键均匀的映射到散列表的不同位置。
如果散列表存储的链表很长,散列表的速度将急剧下降。然而,如果使用的散列函数很好,这些链表就不会很长!
性能
在平均的情况下,散列表执行各种操作的时间都为O(1),O(1)被称为常量时间。
简单查找的运行时间为线性时间。 O(n) -------线性时间
二分查找的速度更快,所需的时间为 对数时间。 O(log n) -------对数时间
在散列表中查找所花费的时间为常量时间。 O(1)-----------常量时间
但是 在最糟糕的情况下,散列表的各种操作的速度都很慢,因此,在使用散列表时,避开最糟糕情况至关重要。为此,需要避免冲突。而要避免冲突,需要有:
较低的填装因子;
良好的散列函数。
本章小结
散列表是一种功能强大的数据结构,其操作速度快,还能让你以不同的方式简历数据模型。你可能很快会发现自己经常在使用它。
1、你可以结合散列函数和数组来创建散列表。
2、冲突很糟糕,你应使用可以最大限度减少冲突的散列函数。
3、散列表的查找、插入和删除速度都非常的快。
4、散列表适合用于模拟映射关系
5、一旦填装因子超过0.7,就该调整算列表的长度。
6、散列表可用于缓存数据,例如在web服务器上。
7、散列表非常适合用于防止重复。