哈希表——聊聊查找的那些事情
尽管很多人知道哈希表,但并并没有掌握到核心(到目前为止,我也没有)。其实对于哈希的阐述,应该顺着这样的一个结构:
什么是哈希结构?
为什么需要哈希结构?
如何实现哈希结构?
实现哈希结构的过程中会遇到什么问题?
如何解决这些存在的问题?
实际上,学习的每个过程,几乎都遵循着这样的一个逻辑,但是关键在于,很多时候,我们恰恰陷入了局部,却没有认识整体,直接导致我们对问题的理解不深刻:
什么是哈希结构呢?
在此不给出抽象的定义,谈谈个人对于哈希的理解:利用某种方法(数学映射,也就是哈希函数),将我们的关键字 映射到 某个 存储单元,以便我们能够以最快的速度找到它!
举一个最直观的例子,当你查字典的时候,想想你是怎么查的,你并不是从第一页翻到最后一页吧,而是通过拼音查找法,或者偏旁部首查找法,这样可以更快速的查找到我们要找的东西。而这样做之所以能够快速找到,是因为我们利用了我们要找的 关键字 本身的一些信息:它的拼音 以及偏旁部首。也就是,我们并不是无脑的从第一页翻到了最后一页,而是我们发现:可以利用 关键字自身的特点,来制定某种规则,来方便我们查找。
实际上,哈希表就利用了这样一种朴素的思想:利用了 待查找的关键字的特征,建立了某种映射关系,从而使得我们可以快速定位与查找。
为什么需要哈希结构呢?
我们需要形成一种意识:数据结构存在的意义是什么? 答案只有一个:提高查找、插入、删除的效率。
从上述对哈希表的论述中可以看出,哈希表的存在主要是为了提高查找的效率。那么,我们来看看其他的结构,到底问题在哪呢?
1 数组:数组就像一张空白的纸,来一个数值按顺序填入纸张,最后当我们要查找某个数时,我们还得从第一个开始比对,糟糕的情况下,我们需要比对到最后一个,也就是其时间复杂度为O(n)。那么为什么会出现这种情况呢?本质在于,我们只是不加区分的按序存储,没有利用待查找关键字 自身的特征。(比如如果在创建数组的时候,我们奇数放在一栏,偶数放在一栏这样会快很多)。
2 链表?不好意思,链表并不适合查找
3 二叉查找树:二叉查找树 在建立“表”的时候,将这个表建立成了一个“顺序表”,使得中序遍历可以得到一个数值从小到大的“表”。二叉查找树利用了二分法,提高了查找速度,时间复杂度为O(logn),相比于数组,已经是个很大的提升。那么能够提升的本质是什么? 我们不再是不加区分的按序存储,而是将大小进行排序,构建了一棵树。我们将 关键字 按照大小排序实质上就是利用了数值本身的特性。
上述改进其实提供了一种思想:我们要向获得更快的速度,就需要利用上数据本身的信息(无论是干什么,这种思想都会用的上,包括AI,定位等等)。
那么,利用大小排序“用尽了”数据本身的特性吗?
显然没有, 这种时候,哈希表通过构建一个哈希函数,更大化的利用了数据本身 的特性,而不再是 仅仅比较大小,进行排序这种操作了。如果我们构建的哈希函数足够完美,能构成 一 一映射的关系,那么我们查找的速度就是O(1),怎么样,是不是很快?
可见,只有充分挖掘数据本身的特性,我们才有可能获得更好的算法(这是一种思想)
理想很美好,现实很骨感,我们并非都是天才,构建一个完美的哈希函数并不是一件容易的事情。在这种情况下,哈希表将会暴露出一些很重要不能被忽略的问题:
哈希冲突!
哈希均衡!
这两个问题,可以说是十分重要的一个问题了。时间有限,不再赘述,网上资料很多。只是想说明一点:哈希冲突和哈希均衡是设计哈希表时必须要考虑的问题。
除了这个以外:
当 当前哈希表趋于满的时候,扩展哈希,或者说是再哈希 也是一个重要问题。先不赘述。
针对哈希结构,我们需要注意以下问题:
选取何种方式构建哈希函数
如何应对哈希冲突
如何应对哈希均衡
怎样理解 负载因子?
再哈希 需要考虑哪些问题?