本文从数据结构角度认识哈希表,更多关于Java编程中HashMap的用法见:对HashMap的简单认识
1. 什么是哈希表
哈希表名字源于 Hash,也可以叫作散列表。哈希表是一种特殊的数据结构,它与数组、链表以及树等数据结构相比,有很明显的区别。
2. 哈希表的核心思想
在数组、链表以及树等数据结构中,数据的存储位置和数据的具体数值之间不存在任何关系。因此,在面对查找问题时,这些数据结构必须采取逐一比较的方法去实现。
而哈希表的设计采用了函数映射的思想,将记录的存储位置与记录的关键字关联起来。这样的设计方式,能够快速定位到想要查找的记录,而且不需要与表中存在的记录的关键字比较后再来进行查找。
我们先回顾一下数组的查找操作。数组是通过数据的索引(index) 来取出数值的,例如要找出 a 数组中,索引值为 1 的元素。在前面的课时中,我们讲到索引值是数据存储的位置,因此,直接通过 a[1]
就可以取出这个数据。通过这样的方式,数组实现了“地址 = f (index)
”的映射关系。
如果用哈希表的逻辑来理解的话,这里的 f ()
就是一个哈希函数。它完成了索引值到实际地址的映射,这就让数组可以快速完成基于索引值的查找。然而,数组的局限性在于,它只能基于数据的索引去查找,而不能基于数据的数值去查找。
如果有一种方法,可以实现“地址 = f (关键字)
”的映射关系,那么就可以快速完成基于数据的数值的查找了。这就是哈希表的核心思想。
下面我们通过一个例子来体会一下。
假如,我们要对一个手机通讯录进行存储,并要根据姓名找出一个人的手机号码,如下所示:
张一:155555555
张二:166666666
张三:177777777
张四:188888888
一个可行的方法是,定义包含姓名、手机号码的结构体,再通过链表把 4 个联系人的信息存起来。当要判断“张四”是否在链表中,或者想要查找到张四的手机号码时,就需要从链表的头结点开始遍历。依次将每个结点中的姓名字段,同“张四”进行比较。直到查找成功或者全部遍历一次为止。显然,这种做法的时间复杂度为 O(n)。
如果要降低时间复杂度,就需要借助哈希表的思路,构建姓名到地址的映射函数“地址 = f (姓名)
”。这样,我们就可以通过这个函数直接计算出”张四“的存储位置,在 O(1) 时间复杂度内就可以完成数据的查找。
通过这个例子,不难看出 Hash 函数设计的好坏会直接影响到对哈希表的操作效率。假如对上面的例子采用的 Hash 函数为,姓名的每个字的拼音开头大写字母的 ASCII