哈希查找
什么是哈希表(Hash)?
我们使用一个下标范围比较大的数组来存储元素。 可以设计一个函数(哈希函数, 也叫做散列函数), 使得每个元素的关键字都与一个函数值(即数组下标)相对应, 于是用这个数组单元来存储这个元素;也可以简单的理解为, 按照关键字为每一个元素"分类",然后将这个元素存储在相应"类"所对应的地方。 但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素, 却计算出了相同的函数值,这样就产生了"冲突",换句话说,就是把不同的元素分在了相同的"类"之中。 后面我们将看到一种解决"冲突"的简便做法。
总的来说,"直接定址"与"解决冲突"是哈希表的两大特点。
什么是哈希函数?
哈希函数的规则是:通过某种转换关系,使关键字适度的分散到指定大小的的顺序结构中,越分散, 则以后查找的时间复杂度越小,空间复杂度越高。
算法思想
哈希的思路很简单,如果所有的键都是整数,那么就可以使用一个简单的无序数组来实现:将键作为索引, 值即为其对应的值,这样就可以快速访问任意键的值。这是对于简单的键的情况,我们将其扩展到可以处理更加复杂的类型的键。
思路
- 用给定的哈希函数构造哈希表;
- 根据选择的冲突处理方法解决地址冲突;常见的解决冲突的方法:拉链法和线性探测法。
- 在哈希表的基础上执行哈希查找。
哈希表是一个在时间和空间上做出权衡的经典例子。如果没有内存限制,那么可以直接将键作为数组的索引。 那么所有的查找时间复杂度为O(1);如果没有时间限制,那么我们可以使用无序数组并进行顺序查找, 这样只需要很少的内存。哈希表使用了适度的时间和空间来在这两个极端之间找到了平衡。 只需要调整哈希函数算法即可在时间和空间上做出取舍。
复杂度分析:
单纯论查找复杂度:对于无冲突的Hash表而言,查找复杂度为O(1)(注意,在查找之前我们需要构建相应的Hash表)。
使用Hash,我们付出了什么?
我们在实际编程中存储一个大规模的数据,最先想到的存储结构可能就是map, 也就是我们常说的KV pair,经常使用Python的博友可能更有这种体会。 使用map的好处就是,我们在后续处理数据处理时,可以根据数据的key快速的查找到对应的value值。 map的本质就是Hash表,那我们在获取了超高查找效率的基础上,我们付出了什么?
Hash是一种典型以空间换时间的算法,比如原来一个长度为100的数组,对其查找, 只需要遍历且匹配相应记录即可,从空间复杂度上来看,假如数组存储的是byte类型数据, 那么该数组占用100byte空间。现在我们采用Hash算法,我们前面说的Hash必须有一个规则, 约束键与存储位置的关系,那么就需要一个固定长度的hash表,此时,仍然是100byte的数组, 假设我们需要的100byte用来记录键与位置的关系,那么总的空间为200byte, 而且用于记录规则的表大小会根据规则,大小可能是不定的。
代码实现