本文转载自: http://rock3.info/blog/2013/12/05/hashtable%E6%B5%85%E6%9E%90/

一、Hash特点

Hash,就是杂凑算法,Hash(str1)=str2,具备四种特性:

  • 长变短:Hash算法可以将任意长度的数据Hash成固定长度的数据。
  • 速度快:Hash算法基本上是异或和位移操作,速度很快。
  • 不可逆:由hash结果找到hash前的字符串是困难的。
  • 低碰撞:存在这样的情况,Hash前输入不同,Hash后输出相同,但绝大多数情况是输入不同,输出不同。

二、HashTable

据《算法导论》上讲:“很多应用中,都需要一种动态的集合结构,它仅仅支持INSERT、SEARCH和DELETE字典操作...实现字典操作的一种有效的数据结构为散列表(Hash Table)...在散列表中,查找一个元素的时间与在链表中查找一个元素的时间相同,最坏情况下都是O(n)...散列表是普通数组概念的推广...”。也就是说,Hash Table是为了解决动态的插入、搜索、删除等操作,而专门设置的一种数据结构,目的为了降低这些操作的时间复杂度。

这里面有个“字典操作”,“字典”模型时这样的:通过某个关键字,能够查到该关键字相关的信息,比如通过身份证号,可以查到姓名、性别、年龄、婚否等信息。这样,就抽象出两个关键的部分key和value,身份证号码——key,姓名、性别、年龄、婚否——value。考虑普通的数据存储方式——数组和链表,来存储key和value,先假设指有100个元素,对于数组通常这样存:

struct person {

char key[128];

struct value v;

}p[100];

对于链表这样存:

struct person_list{

struct person_list *next;

char key[128];

struct value v;

};

下文中,将上图中的1,2,3,4,5,6这样的数组节点称为“数组节点“,而对p1,p4,p9,p12,p13,p15这种具体的代表key和value的结构体链表节点,称为Entry。

对于INSERT、SEARCH和DELETE,以及存储空间的影响(假设key两两不同,插入、搜索、删除均以key为对象):

显示 
10
25
50
100
 项结果
搜索:
  INSERT SEARCH DELETE 存储空间
数组 需要移动元素,比较费劲 与数组长度有关,O(n) 查找到以后删除,O(n) 紧凑
链表 对于指定的插入位置,改变指针即可,较方便 逐个查找,查找时间与链表长度有关n,O(n) 需要先查找,找到位置后,改变指针即可,O(n) 使用了链表指针,多用了一些空间
显示第 1 至 2 项结果,共 2 项
上页下页

为了平衡数组不易插入,链表、数组均不易索引的问题,很容易想到将key值转化为数组的下标、建立一个映射(将字符串转化为整数,或者直接就是整数,先这么简单理解,这也可能产生碰撞),然后再通过指针的方式指向具体的元素,即能快速查找,又能方便插入、删除:

这实际上已经有了Hash Table的原型:“横向数组,纵向链表”,key到下标的映射就是Hash Table中的hash。下面的样子显得更“好看”一些,这就是一种常见的Hash Table实现:

横向数组的下标为key的hash值,纵向链表为hash值相同的元素组成的链表(这里就是举个例子,hash不会产生非常多的碰撞,hash值也的长度也是较长的)。这样做有如下好出:

  • 由于hash计算速度快,对于任意一个key,可以快速的找到其所属的链表(O(n),n为hash数组的长度)。
  • 在链表里进行插入、删除操作,比较方便(O(a),取决于链表中元素的个数a)。
  • 整体上可以将查找的时间复杂度从原来的O(x)降低到O(1+a),其中x为原来Entry的条目数量,a=x/n,也即平均每个链表元素的个数,也叫装载因子。

但是毕竟由于碰撞的存再,使得搜索的时间复杂度没能够达到O(1)。Hash Table的一个关键工作就是尽量的降低碰撞。

四、“链接法”和“开放寻址法”

上面一个图画得就是“链接法”:对于碰撞,选择在相同的数组节点上建立链接。还有一中方法来处理“碰撞”——“开放寻址法”(时间复杂度更低):每个数组节点上就一个元素,如果待插入的Entry计算出来的hash值所在的数组节点非空闲(已经有一个Enry了),就采取某种方法再选择一个空闲的数组节点,插入该Enry。这种情况下,整个数组必须支持动态扩容:当数组空闲节点低于一个阀值时,将扩展数组容量为原来的一倍。这个阀值通常是0.72,也即数组节点有72%的比例为非空闲,就需要将数组扩容至原来的2倍。

“开放寻址法”中如何找到下一个空闲数组,有以下几种方法但并不局限于以下方法,这里就不展开了:

  • 线形探查
  • 二次探查
  • 双重散列(较好)

五、HashTable的搜索复杂度

HashTable平衡了查找速度、插入速度,但是某些情况下,碰撞是不可避免的,只要有碰撞存在,就无法使搜索的时间复杂度达到O(1)。

对于“链接法”,搜索的时间复杂度为O(1+a),a为转载因子。而对于“开放寻址法”,因为每个数组节点上就1个Entry,基本上能够达到O(1)。

还有一种“完全散列”最好,能够使得最坏的情况时间复杂度仍然为O(1),这里就不讨论了。

因此,如果简单的用一下hash table,可以采用“链接法”,如果追求速度,或者数据量比较大,应该采用“完全散列”或者“开放寻址法”(我猜的,没验证)。

六、HashTable的应用

估计,HashTable肯定会应用到数据库的实现中,数据库是典型的字典模型。另外HashTable在Linux内核中也有应用,很多场景均使用了hash table(hlist),如tasklet、页表维护等,其类型定义于include/linux/types.h:

1
2
3
4
5
6
7
struct hlist_head {
    struct hlist_node *first;
};
 
struct hlist_node {
    struct hlist_node *next, **pprev;
};

Linux内核中的hlist采用的是”链接法“,此处不展开了。

七、总结

java里有HashMap这个词,看了一些文章,感觉与《算法导论》上说的Hash Table没有太大的区别,只不过是java的一种实现而已,java里面也有一种叫做HashTable的,是散列表的不同实现。

HashTable是根据Hash的特点去解决这种问题:海量数据的索引、插入、删除时,可以先hash一下,将海量数据进行分块,然后再进行搜索、插入、删除等操作,以便降低时间复杂度。在最好的情况下,能够将时间复杂度降低到O(1)。

HashTable的“横向数组、纵向链表”的样子,可以以“链接法”解决时间复杂度的问题,也可以以“开放寻值法”解决时间复杂度的问题,后者每个数组节点上就一个元素,hash后在相应的数组节点位置顺序向后查找,在找到的第一个位置插入该元素,这种情况下,整个数组必须支持动态扩容:当数组空闲节点低于一个阀值时,将扩展数组容量为原来的一倍。这个阀值通常是0.72。

实际上Hash以后可以接多种数据结构,HashTable就是接的链表,如果衔接树也是可以的,就是HashTree。

存疑:

1、我列的hash特点是传统的md5、sha1等hash算法的特点,hash table中用的hash算法比md5、sha1要简单很多,可能有一定的区别。

2、感觉对hash  table的理解还是不太到位,《算法导论》上比较关注hash的构造方法,怎么才能构造出好的hash,但在我理解,hash就是一个使用O(1)时间完成长变短的操作,至于分布是不是均匀,那就看设计了,设计的好一些可能更均匀吧(还得看输出,不仅仅是hash算法本身),但不好也就哪样了。但是《算法导论》上认为分布是否均匀与查找的时间复杂度密切相关,而且应该尽量避免碰撞的情况,并给出了几种处理碰撞的方法。

3、关于hash的设计的方法和技巧,如何能够更加均匀分布,如何尽量快,如何碰撞少,不列了,可能里面涉及比较多的数学知识。

HashTable浅析的更多相关文章

  1. 浅析Hashmap和Hashtable

    一.Hashmap不是线程安全的,而Hashtable是线程安全的 通过查看源码可以发现,hashmap类中的方法无synchronized关键字,而hashtable类中的方法有synchroniz ...

  2. 浅析HashMap和Hashtable的区别

    HashMap和Hashtable两个类都实现了Map接口,二者保存键值对(key-value对): HashMap和HashTable区别 第一,继承的父类不同.HashMap继承自Abstract ...

  3. 关于HashMap,HashTable,HashSet浅析

    首先,最重要的,HashMap  作为一个我们使用非常多的集合.最常被大家认知的是,它是一个key-value形式存储数据的数据结构,可以实现快速的存,取操作.  关于HashMap的源码,我们截取一 ...

  4. C# 程序性能提升篇-1、装箱和拆箱,枚举的ToString浅析

    前景提要: 编写程序时,也许你不经意间,就不知不觉的使程序代码,发生了装箱和拆箱,从而降低了效率,不要说就发生那么一次两次,如果说是程序中发生了循环.网络程序(不断请求处理的)等这些时候,减少装箱和拆 ...

  5. C#基础复习(4) 之 浅析List、Dictionary

    参考资料 [1] .netCore 源码 https://github.com/dotnet/corefx [2] <Unity 3D脚本编程 使用C#语言开发跨平台游戏>陈嘉栋著 [3] ...

  6. java之Map源代码浅析

    Map是键值对.也是经常使用的数据结构. Map接口定义了map的基本行为.包含最核心的get和put操作,此接口的定义的方法见下图: JDK中有不同的的map实现,分别适用于不同的应用场景.如线程安 ...

  7. Java集合框架之Map接口浅析

    Java集合框架之Map接口浅析 一.Map接口综述: 1.1java.util.Map<k, v>简介 位于java.util包下的Map接口,是Java集合框架的重要成员,它是和Col ...

  8. fastjson =< 1.2.47 反序列化漏洞浅析

    fastjson =< 1.2.47 反序列化漏洞浅析 iiusky 洛米唯熊 今天 文章出处: https://www.03sec.com/3240.shtmlhttps://www.secq ...

  9. 浅析Java7中的ConcurrentHashMap

    引入ConcurrentHashMap 模拟使用hashmap在多线程场景下发生线程不安全现象 import java.util.HashMap; import java.util.Map; impo ...

随机推荐

  1. 【转】Lisp 已死,Lisp 万岁!

    Lisp 已死,Lisp 万岁! 有一句古话,叫做“国王已死,国王万岁!”它的意思是,老国王已经死去,国王的儿子现在继位.这句话的幽默,就在于这两个“国王”其实指的不是同一个人,而你咋一看还以为它自相 ...

  2. linux下配置tomcat集群的负载均衡

    linux下配置tomcat集群的负载均衡 一.首先了解下与集群相关的几个概念集群:集群是一组协同工作的服务实体,用以提供比单一服务实体更具扩展性与可用性的服务平台.在客户端看来,一个集群就象是一个服 ...

  3. [转]Intellij idea创建javaWeb以及Servlet简单实现

    准备:1. 安装jdk1.72. 安装tomcat1.8 一.创建并设置javaweb工程 1.创建javaweb工程File --> New --> Project... 设置工程名字: ...

  4. 微服务,ApiGateway 与 Kong

    一. 微服务 二. Api Gateway 三. Kong 的使用 一. 微服务 对于一些传统的 大型项目,传统的方式会有一些缺陷,比如说 新人熟悉系统成本高(因为整个系统作为一个整体,彼此会有一定的 ...

  5. mysql把主键定义为自动增长标识符类型

    分享下mysql中如何把主键定义为自动增长标识符类型. 1.把主键定义为自动增长标识符类型在mysql中,如果把表的主键设为auto_increment类型,数据库就会自动为主键赋值.例如: )); ...

  6. Xilinx vivado迅雷下载地址(所有版本)

    注:其实该方法适用于提取Xilinx官网的任意工具的任意版本的迅雷下载地址 ①进入Xilinx官网,进入Device->Design Tools,选择你想要下载的任意工具.②进入新web页面,右 ...

  7. ubuntu svn rabbitvcs 安装

    参考链接:http://blog.csdn.net/weng_xianhu/article/details/10079179 32位系统完全按照上面链接安装没有问题,但是如果是64位系统,第六步下载资 ...

  8. Adding support for distinct operation for table API on DataStream

    https://github.com/apache/flink/pull/6521/files/66c3bd5d52a5e4af1f83406035b95774e8b6f636#diff-680b30 ...

  9. Swift 开发中,为什么要远离 Heap?

    Swift 开发中,为什么要远离 Heap? WWDC的视频 — Understanding Swift Performance 中,苹果上来就说,Heap 的操作复杂度要远远超越 Stack.所以大 ...

  10. 【C/C++】void指针知多少

    void指针 void指针平时用的地方不多,但是不代表它不重要 #include <stdio.h> typedef ]; //指针数组的指针类型 int main(void) { voi ...