哈希表其实是一种散列表,应用散列技术将数据使用 key 值进行记录,散列技术是在记录位置和它的关键字之间建立一个确定的对应关系 f,使得每个关键字 key 对应一个存储位置 f(key)。
举个例子:有 100w 个数据,它的范围是 0-99,如果使用一般的存储方法去存储或对他进行排序访问操作的话将会很复杂,这个时候我们就可以建立一个哈希表,如定义一个元素个数为 100 的数组,用数组下标表示数据的值,数组中的值表示该下标数据的个数。这样一个简单的哈希表就建立好了,这样我们去访问这些数据就直接访问数组下标就好了,当然这只是最简单的一种情况,如果要保存的数据是字符串,浮点数,无限制整数,或是数组,结构体之类的数据,这个时候我们该怎么去建立哈希表去对其进行保存呢?
此时我们就需要搞定一个哈希函数用来限制这些数据,使其规则的落在一个范围内,比如一堆字符串数据,我们可以对每个字符串取每个字符的 ASCII 码值相加然后对 100 求余,就也可以使用 100 个元素的数组来表示这个字符串了,例如 abc 对应哈希表的位置就是(97+98+99)%100=94,同样的浮点数,无限制整数一样可以使用不同的哈希函数使其限制在一个表值范围内,这就是我们的哈希表的实现了但是聪明的学生就发现了,这样必须会产生数据冲突,例如 45 和 145,如果采用100 个元素的哈希表对其存储,那么数组下标为 45 的地方就无法分便表示的是 45 的值还是 145 的值,或者 abc 和 bca 如果采用上面的哈希函数存储在哈希表中,也会产生相同的冲突情况,那面对这种冲突该怎么解决?这就要涉及到数据结构中一个很重要的知识点了,解决哈希冲突。
如何解决哈希冲突问题
开放定址法
开放定址法的思想就好像
我们去教室上课,发现该位置已经存在人了,所以我们应该寻找新的位子坐下。如何寻找新的位置就通过以下几种方法实现。
(1).线性探测法
当我们的所需要存放值的位置被占了,我们就往后面一直加 1 并对 m 取模直到存在一个空余的地址供我们存放值,取模是为了保证找到的位置在 0~m-1 的有效空间之中。
一般哈希公式: h(x)=(Hash(x)+i)mod (Hashtable.length); (i 会逐渐递增加 1 )
示例:
但这种一般只能单方向向后搜索空位置,容易存在两个不同的哈希值抢占同一个哈希地址的情况,所以就有下面一种开放地址法
(2).平方探测法(二次探测法)
当我们的所需要存放值的位置被占了,会前后寻找而不是单独方向的寻找。
一般哈希公式: h(x)=(Hash(x) +i)mod (Hashtable.length); (i 依次为 +(i^2) 和 -(i^2) )
示例:
再哈希法
同时构造多个不同的哈希函数,等发生哈希冲突时就使用第二个、第三个……等其他的哈希函数计算地址,直到不发生冲突为止。虽然不易发生聚集,但是增加了计算时间。
链地址法
将所有哈希值相同的数据都按顺序记录在哈希表对应的链表里面
示例:
哈希表在 C++中的体现
在 C++STL 容器中,unordered 系列的关联式容器
之所以效率比较高,是因为其底层使用了哈希结构,如 unordered_map,unordered_set.