上篇文章的查找是不是有意犹未尽的感觉呢?因为我们是真真正正地接触到了时间复杂度的优化。从线性查找的 O(n) 直接优化到了折半查找的 O(logN) ,绝对是一个质的飞跃。但是,我们的折半查找最核心的一个要求是什么呢?那就是必须是原始数据是要有序的。这可是个麻烦事啊,毕竟如果数据量很庞大的话,排序又会变得很麻烦。不过别着急,今天我们要学习的散列表查找又是另一种形式的查找,它能做到什么程度呢?

O(1) ,是的,你没看错,散列表查找在最佳情况下是可以达到这种常数级别的查找效率的,是不是很神奇。

哈希散列(除留余数法)

先通过实际的例子看一种非常简单的散列算法。在数据量比较大的情况下,我们往往要对数据表进行表操作,最简单的一种方案就是根据某一个字段,比如说 ID 来对它进行取模。也就是说,假如我们要分20张表,那么就用数据的 ID 来除以 20 ,然后获得它的余数。然后将这条数据添加到余数所对应的这张表中。我们通过代码来模拟这个操作。

or($i=0;$i<100;$i++){
$arr[] = $i+1;
} $hashKey = 7;
$hashTable = [];
for($i=0;$i<100;$i++){
$hashTable[$arr[$i]%$hashKey][] = $arr[$i];
} print_r($hashTable);

在这里,我们假设是将 100 条数据放到 7 张表中,就是直接使用取模运算符 % 来获取余数就行了,接着就将数据放入到对应的数组下标中。这 100 个数据就被分别放置在了数组中 0-6 的下标中。这样,我们就实现了最简单的一种数据分表的思想。当然,在实际的业务开发中要远比这个复杂。因为我们考虑各种不同的场景来确定到底是以什么形式进行分表,分多少张表,以及后续的扩展情况,也就是说,真实情况下要比我们这里写的这个复杂很多。

做为演示代码来说,这种分表的散列形式其实就是散列表查找中最经典也是使用最多的除留余数法。其实还有其它的一些方法,比如平方取中法、折叠法、数字分析法之类的方法。它们的核心思想都是作为一个散列的哈希算法,让原始数据对应到一个新的值(位置)上。

类似的思想其实最典型的就是 md5() 的散列运算,不同的内容都会产生不同的值。另外就是 Redis 、 Memcached 这类的键值对缓存数据库,它们其实也会将我们设置的 Key 值进行哈希后保存在内存中以实现快速的查找能力。

散列冲突问题(线性探测法)

上面的例子其实我们会发现一个问题,那就是哈希算法的这个值如果很小的话,就会有很多的重复冲突的数据。如果是真实的一个存储数据的散列表,这样的存储其实并不能帮我们快速准确的找到所需要的数据。查找查找,它核心的能力其实还是在查找上。那么如果我们随机给定一些数据,然后在同样长度的范围内如何保存它们并且避免冲突呢?这就是我们接下来要学习的散列冲突要解决的问题。

$arr = [];
$hashTable = [];
for($i=0;$i<$hashKey;$i++){
$r = rand(1,20);
if(!in_array($r, $arr)){
$arr[] = $r;
}else{
$i--;
}
} print_r($arr);
for($i=0;$i<$hashKey;$i++){
if(!$hashTable[$arr[$i]%$hashKey]){
$hashTable[$arr[$i]%$hashKey] = $arr[$i];
}else{
$c = 0;
echo '冲突位置:', $arr[$i]%$hashKey, ',值:',$arr[$i], PHP_EOL;
$j=$arr[$i]%$hashKey+1;
while(1){
if($j>=$hashKey){
$j = 0;
}
if(!$hashTable[$j]){
$hashTable[$j] = $arr[$i];
break;
}
$c++;
$j++;
if($c >= $hashKey){
break;
}
}
}
}
print_r($hashTable);

这回我们只生成 7 个随机数据,让他们依然以 7 为模进行除留取余。同时,我们还需要将它们以哈希后的结果保存到另一个数组中,可以将这个新的数组看做是内存中的空间。如果有哈希相同的数据,那当然就不能放在同一个空间了,要不同一个空间中有两条数据我们就不知道真正要取的是哪个数据了。

在这段代码中,我们使用的是开放地址法中的线性探测法。这是最简单的一种处理哈希冲突的方式。我们先看一下输出的结果,然后再分析冲突的时候都做了什么。

// Array
// (
// [0] => 17 // 3
// [1] => 13 // 6
// [2] => 9 // 2
// [3] => 19 // 5
// [4] => 2 // 2 -> 3 -> 4
// [5] => 20 // 6 -> 0
// [6] => 12 // 5 -> 6 -> 0 -> 1
// )
// 冲突位置:2,值:2
// 冲突位置:6,值:20
// 冲突位置:5,值:12
// Array
// (
// [3] => 17
// [6] => 13
// [2] => 9
// [5] => 19
// [4] => 2
// [0] => 20
// [1] => 12
// )
  • 首先,我们生成的数字是 17、13、9、19、2、20、12 这七个数字。

  • 17%7=3,17 保存到下标 3 中。

  • 13%7=6,13 保存到下标 6 中。

  • 9%7=2,9 保存到下标 2 中。

  • 19%7=5,19 保存到下标 5 中。

  • 2%7=2,好了,冲突出现了,2%7 的结果也是 2 ,但是 2 的下标已经有人了,这时我们就从 2 开始往后再看 3 的下标有没有人,同样 3 也被占了,于是到 4 ,这时 4 是空的,就把 2 保存到了下标 4 中。

  • 20%7=6,和上面一样,6 已经被占了,于是我们回到开始的 0 下标,发现 0 还没有被占,于是 20 保存到下标 0 中。

  • 最后的 12%7=5,它将依次经过下标 5 、6 、0、1 最后放在下标 1 处。

最后生成的结果就是我们最后数组输出的结果。可以看出,线性探测其实就是如果发现位置被人占了,就一个一个的向下查找。所以它的时间复杂度其实并不是太好,当然,最佳情况是数据的总长度和哈希键值的长度相吻合,这样就能达到 O(1) 级别了。

当然,除了线性探测之外,还有二次探测(平方)、伪随机探测等算法。另外也可以使用链表来实现链地址法来解决哈希冲突的问题。这些内容大家可以自己查阅一下相关的文档或书籍。

总结

哈希散列最后的查找功能其实就和我们上面生成那个哈希表的过程一样,发现有冲突的解决方式也是一样的,这里就不多说了。对于哈希这块来说,不管是教材还是各类学习资料,其实介绍的内容都并不是特别的多,所以,我们也是以入门的心态来简单地了解一下哈希散列这块的知识,更多的内容大家可以自己研究多多分享哈!

测试代码:

https://github.com/zhangyue0503/Data-structure-and-algorithm/blob/master/6.查找/source/6.2散列表查找.php

参考文档:

《数据结构》第二版,严蔚敏

《数据结构》第二版,陈越

关注公众号:【硬核项目经理】获取最新文章

添加微信/QQ好友:【xiaoyuezigonggong/149844827】免费得PHP、项目管理学习资料

知乎、公众号、抖音、头条搜索【硬核项目经理】

B站ID:482780532

【PHP数据结构】散列表查找的更多相关文章

  1. 数据结构---散列表查找(哈希表)概述和简单实现(Java)

    散列表查找定义 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,是的每个关键字key对应一个存储位置f(key).查找时,根据这个确定的对应关系找到给定值的key的对应f(key) ...

  2. 数据结构(四十二)散列表查找(Hash Table)

    一.散列表查找的基础知识 1.散列表查找的定义 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key).查找时,根据这个确定的对应关系找到 ...

  3. Python数据结构——散列表

    散列表的实现常常叫做散列(hashing).散列仅支持INSERT,SEARCH和DELETE操作,都是在常数平均时间执行的.需要元素间任何排序信息的操作将不会得到有效的支持. 散列表是普通数组概念的 ...

  4. 哈希表查找(散列表查找) c++实现HashMap

    算法思想: 哈希表 什么是哈希表 在前面讨论的各种结构(线性表.树等)中,记录在结构中的相对位置是随机的,和记录的关键字之间不存在确定的关系,因此,在结构中查找记录时需进行一系列和关键字的比较.这一类 ...

  5. python 散列表查找

    class HashTable: def __init__(self, size): self.elem = [None for i in range(size)] self.count = size ...

  6. 哈希表(散列表),Hash表漫谈

    1.序 该篇分别讲了散列表的引出.散列函数的设计.处理冲突的方法.并给出一段简单的示例代码. 2.散列表的引出 给定一个关键字集合U={0,1......m-1},总共有不大于m个元素.如果m不是很大 ...

  7. hashtable——散列表

    2018-11-01 散列表---哈希表基于快速存取,时间换空间一种基于线性数组的线性表,不过元素之间并非紧密排列 散列函数--通过函数,有key关键码计算地址(相当于数组下标),函数尽可能使元素均匀 ...

  8. 【Java】 大话数据结构(13) 查找算法(4) (散列表(哈希表))

    本文根据<大话数据结构>一书,实现了Java版的一个简单的散列表(哈希表). 基本概念 对关键字key,将其值存放在f(key)的存储位置上.由此,在查找时不需比较,只需计算出f(key) ...

  9. JavaScript数据结构——集合、字典和散列表

    集合.字典和散列表都可以存储不重复的值. 在集合中,我们感兴趣的是每个值本身,并把它当作主要元素.在字典和散列表中,我们用 [键,值] 的形式来存储数据. 集合(Set 类):[值,值]对,是一组由无 ...

随机推荐

  1. Subversion Backup and Restore

    Backup Specified Revision Backup specified revision (here is 20): $ cd /opt/svnRepo $ svnadmin dump ...

  2. Nginx搭建与相关配置

    目录 一.Nginx简介 1.1 概述 1.2 Nginx与Apache的差异 二.编译安装Nginx服务 2.1 将nginx软件包传到主机/opt目录下 2.2.安装依赖包 2.3.添加模块编译安 ...

  3. Spring源码解析之ConfigurationClassPostProcessor(一)

    ConfigurationClassPostProcessor 在前面一个章节,笔者和大家介绍了在构造一个应用上下文时,spring会执行到PostProcessorRegistrationDeleg ...

  4. netty系列之:netty中的懒人编码解码器

    目录 简介 netty中的内置编码器 使用codec要注意的问题 netty内置的基本codec base64 bytes compression json marshalling protobuf ...

  5. C++ com 组件的使用

    // CommonTest.cpp : This file contains the 'main' function. Program execution begins and ends there. ...

  6. 【springcloud】客户端负载均衡(Ribbon)

    转自:https://blog.csdn.net/pengjunlee/article/details/86594934 服务器端负载均衡负载均衡是我们处理高并发.缓解网络压力和进行服务器扩容的重要手 ...

  7. 简陋的Excel到MYSQL的数据传输JAVA实现

    实现从excel读取数据,使用的是jxl.jar(到处都有,请大家随意下载),其中封装好了通过excel提供的接口,对excel中的数据库进行读取的实现: 先为了熟悉其中的方法使用,做了以下的测试: ...

  8. 使用dom4j工具:xml总结

    1.IO流:BufferedReader字符流readLine();截取 不可行!!!2.XML解析:dom4j查:标签:element("name")elements(" ...

  9. mysql 用 source 导入备份数据

    输入命令:source E:/proDB/start2.sql;

  10. Learning ROS: rostopic pub yaml demo

    官方Tutorials中例程的等效命令: rostopic pub -1 /turtle1/cmd_vel geometry_msgs/Twist -- '{linear:[2.0, 0.0, 0.0 ...