其他分享
首页 > 其他分享> > 高级数据结构

高级数据结构

作者:互联网

1、跳表 Skip List

链表加多级索引的结构,称为跳表。

在原始链表的基础上,对链表建立一级“索引”,down指针,指向下一级结点。在多级索引的条件下,会极大提高查找效率。

其作为一种动态数据结构,在插入和删除操作中,需要维护索引和原始链表之间的平衡。跳表是通过 随机函数 生成值k,那么就将这个结点添加到第一级到第k级,这k级索引中。

2、散列表 Hash Table

1、散列思想

散列表用的是数组支持按照下标随机访问数据的时候,时间复杂度是O(1)的特性。通过散列函数将元素的键值映射为小标,然后将数据存储在数组中对应下标的位置。当按照键值查询元素时,用同样的散列函数,将键值转化为数组下标,从对应的数组下标的位置取数据。

2、散列函数

基本要求:

3、散列冲突

1、开放寻址法

如果出现了散列冲突,就重新探测一个空闲位置,将其插入。

寻找空闲位置的方法:

  1. 线性探测 Linear Probing

 

  1. 二次探测 Quadratic Probing

类似线性探测,线性探测每次探测的步长是 1,那它探测的下标序列就是 hash(key)+0,hash(key)+1,hash(key)+2……而二次探测探测的步长就变成了原来的“二次方”,也就是说,它探测的下标序列就是 hash(key)+0,hash(key)+1²,hash(key)+2²……

  1. 双重散列

当对目标值执行散列函数后得到的值,该值的存储位置已经被占用,则使用第二个散列函数,依次类推,直到找到空闲的存储位置。

优点:

其数据都存储在数组中,可以有效利用CPU缓存加快查询速度,序列化更加简单。

缺点:

删除数据时,需要特殊标记已删除的数据。

因为数据都在一个数组中,冲突的代价更高。

2、链表法

是一种更常用的散列冲突解决办法,在散列表中,每个“桶”或“槽”对应一条链表,所有散列值相同的元素,放到相同槽位对应的链表中。

优点:

内存利用率相对较高,对大装载因子的容忍度更高

缺点:

要额外存储指针,比较消耗内存

链表节点为零散分布,对CPU缓存不友好,影响执行效率

4、装载因子 Load Factor

装载因子 = 填入表元素个数 / 散列表长度

装载因子越大,说明空闲位置越少,冲突越多,散列表性能会下降。

针对散列表,当装载因子过大时,可以进行动态扩容,重新申请一个更大的散列表,将数据搬移到新的散列表。假设每次扩容申请原来两倍的空间,如果原来散列因子是0.8,那么扩容后,散列因子就下降为0.4。

这里也要避免低效的扩容,当散列表很大时,一次性扩容是非常耗时的。

可以将扩容操作穿插在插入操作的过程中,分批完成。当有新数据插入时,将新数据插入到散列表中,并从老的散列表中拿出一个数据放到新散列表,以此类推。通过均摊的方法,使时间复杂度为O(1)。

5、工业级的散列表,java HashMap为例

1、初始大小

默认大小是16,如果可以预估数据量大小,通过修改该值,可以减少动态扩容次数,提高性能。

2、装载因子和动态扩容

最大装载因子默认是0.75,当元素个数超过 0.75*capacity(capacity 表示散列表的容量)的时候,就会启动扩容,每次扩容都会扩容为原来的两倍大小。

3、散列冲突解决办法

链表法

在JDK 1.8中,当链表长度太长(默认超过8)时,链表转换为红黑树。

4、散列函数

其设计并不复杂,最求简单高效、分布均匀。

int hash(Object key) {
    int h = key.hashCode();
    return (h ^ (h >>> 16)) & (capicity -1); //capicity表示散列表的大小
}

hashCode() 返回的是 Java 对象的 hash code

标签:hash,高级,列表,链表,查找,key,数据结构,散列
来源: https://www.cnblogs.com/waitmyself/p/15835198.html