hash表的基础知识

作为一个php程序员,一般常用的数据结构就是数组,基本上数组能解决大部分问题,但是我们人嘛,总要知道点其他的东西。

数据结构大体分为三大类:

线性结构(数组,链表,堆栈,队列),树,图~~~~

对几种简单的数据结构的复杂度进行了解

  1. 数组:属于线性结构的一种,采用一段连续的存储单元来存储数据。对于指定下标的查找,时间复杂度为O(1);通过给定值进行查找,需要遍历数组,逐一比对给定关键字和数组元素,时间复杂度为O(n),当然,也可以用算法进行优化,比如说对于有序数组,则可采用二分查找,插值查找,斐波那契查找等方式,可将查找复杂度提高为O(logn);对于一般的插入删除操作,涉及到数组元素的移动,其平均复杂度也为O(n)。
  2. 线性链表:属于线性结构的一种,对于链表的新增,删除等操作(在找到指定操作位置后),仅需处理结点间的引用即可,时间复杂度为O(1);查找操作需要遍历链表逐一进行比对,复杂度为O(n)。
  3. 二叉树:属于树的一种,对一棵相对平衡的有序二叉树,对其进行插入,查找,删除等操作,平均复杂度均为O(logn)。
  4. 哈希表:相比上述几种数据结构,在哈希表中进行添加,删除,查找等操作,性能十分之高,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1),接下来我们就来看看哈希表是如何实现达到惊艳的常数阶O(1)的。
哈希表概述

数据结构的物理存储结构只有两种:顺序存储结构和链式存储结构(像栈,队列,树,图等是从逻辑结构去抽象的,映射到内存中,也这两种物理组织形式),数组就是顺序存储结构,而哈希表的主干就是数组。

先规范几个接下来会用到的概念。哈希表的本质是一个数组,数组中每一个元素称为一个箱子(bin),箱子中存放的是键值对。f为哈希函数。

哈希表的存储过程如下:

  1. 根据 key ,利用哈希函数计算出它的哈希值 h(整数)= f(关键字)。
  2. 假设箱子的个数为 n,那么这个键值对应该放在第 (h % n) 个箱子中。
  3. 如果该箱子中已经有了键值对,就使用开放寻址法或者拉链法解决冲突。
哈希冲突

如果两个不同的key,通过哈希函数得出的实际哈希值相同怎么办?也就是说,当我们对某个key进行哈希运算,得到相同的哈希值,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。

哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单和散列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),拉链法。
拉链法(借用他图):
hash表的基础知识_第1张图片

负载因子(load factor)

它用来衡量哈希表的 空/满 程度,一定程度上也可以体现查询的效率,计算公式为:

    负载因子 = 总键值对数 / 箱子个数

负载因子越大,意味着哈希表越满,越容易导致冲突,性能也就越低。因此,一般来说,当负载因子大于某个常数(可能是 1,或者 0.75 等)时,哈希表将自动扩容。

哈希表在自动扩容时,一般会创建两倍于原来个数的箱子,因此即使 key 的哈希值不变,对箱子个数取余的结果也会发生改变,因此所有键值对的存放位置都有可能发生改变,这个过程也称为重哈希(rehash)。

但是哈希表的扩容并不总是能够有效解决负载因子过大的问题。假设所有 key 的哈希值都一样,那么即使扩容以后他们的位置也不会变化。虽然负载因子会降低,但实际存储在每个箱子中的链表长度并不发生改变,因此也就不能提高哈希表的查询性能。

所以会发现哈希表的两个问题:

  1. 如果哈希表中本来箱子就比较多,扩容时需要重新哈希并移动数据,性能影响较大。
  2. 如果哈希函数设计不合理,哈希表在极端情况下会变成线性表,性能极低。哈希函数的合理性极为重要。
tip:不同的语言解决重哈希的方式不一样。

你可能感兴趣的:(php,后端,数据结构)