http://blog.csdn.net/yang_yulei/article/details/46337405

在各种数据结构(线性表、树等)中,记录在结构中的相对位置是随机的。因此在机构中查找记录的时需要进行一系列和关键字的比较。这一类的查找方法建立在“比较”的基础上。查找的效率依赖于查找过程中所进行的比较次数。

之前我们介绍的各种基于比较的树查找算法,这些查找算法的效率都将随着数据记录数的增长而下降。仅仅是有的比较慢(时间复杂度为O(n)),有的比较快(时间复杂度是O(logn))而已。这些查找算法的平均查找长度是在一种比较理想的情况下获得的。在实际应用当中,对数据结构中数据的频繁增加和删除将不断地改变着数据的结构。这些操作将可能导致某些数据结构退化为链表结构,那么其性能必然将下降。为了避免出现这种情况而采取的调整措施,又不可避免的增加了程序的复杂程度以及操作的额外时间。

哈希表

理想的情况是希望不经过任何比较,一次存取便能得到所查的记录,那就必须在记的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和一个唯一的存储位置相对应。因而在查找时,只要根据这个对应关系f找到给定值K的像f(K)。由此,不需要进行比较便可直接取得所查记录。在此,我们称这个对应关系为哈希(Hash)函数,按这个思想建立的表为哈希表

在哈希表中对于不同的关键字可能得到同一哈希地址,这种现象称做冲突。在一般情况下,冲突只能尽可能地减少,而不能完全避免。因为哈希函数是从关键字集合到地址集合的映像。通常关键字的集合比较大,它的元素包括所有可能的关键字,而地址集合的元素仅为哈希表中的地址值。在一般情况下,哈希函数是一个压缩映像函数,这就不可避免的要产生冲突。

哈希树(HashTree)算法就是要提供一种在理论上和实际应用中均能有效地处理冲突的方法。一般的哈希(Hash)算法都是O(1)的,而且基本是以空间换时间。这很容易导致对存储空间无限制的需求。本文中哈希树(HashTree)算法在实际操作中使用了一些技巧使得对空间的需求控制在一定范围内。即空间需求仅和所需要存储的对象个数有关,不会无限制地“膨胀”下去。

哈希树的理论基础

【质数分辨定理】

简单地说就是:n个不同的质数可以“分辨”的连续整数的个数和他们的乘积相等。“分辨”就是指这些连续的整数不可能有完全相同的余数序列。

(这个定理的证明详见:http://wenku.baidu.com/view/16b2c7abd1f34693daef3e58.html

例如:

从2起的连续质数,连续10个质数就可以分辨大约M(10) =2*3*5*7*11*13*17*19*23*29= 6464693230 个数,已经超过计算机中常用整数(32bit)的表达范围。连续100个质数就可以分辨大约M(100) = 4.711930 乘以10的219次方。

而按照目前的CPU水平,100次取余的整数除法操作几乎不算什么难事。在实际应用中,整体的操作速度往往取决于节点将关键字装载内存的次数和时间。一般来说,装载的时间是由关键字的大小和硬件来决定的;在相同类型关键字和相同硬件条件下,实际的整体操作时间就主要取决于装载的次数。他们之间是一个成正比的关系。

插入

我们选择质数分辨算法来建立一棵哈希树。

选择从2开始的连续质数来建立一个十层的哈希树。第一层结点为根结点,根结点下有2个结点;第二层的每个结点下有3个结点;依此类推,即每层结点的子节点数目为连续的质数。到第十层,每个结点下有29个结点。

同一结点中的子结点,从左到右代表不同的余数结果。

例如:第二层结点下有三个子节点。那么从左到右分别代表:除3余0,除3余1,除3余2.

对质数进行取余操作得到的余数决定了处理的路径。

结点结构:结点的关键字(在整个树中是唯一的),结点的数据对象,结点是否被占据的标志位(标志位为真时,关键字才被认为是有效的),和结点的子结点数组。

哈希树的节点结构:

 struct Node
{
keyType key ;
ValueType value ;
bool occupied ; //用occupied来表示节点是否被占据。如果节点的关键字(key)有效,那么occupied应该设置位true,否则设置为false。
struct Node* subNodes[] ; //我们用subNodes[i]来表示节点的第i个子节点的地址。(此技术在跳跃表中有介绍,可翻看前面博客)
} ;

(如果在建立当初就建立所有的节点,那么所消耗的计算时间和磁盘空间是巨大的。在实际使用当中,只需要初始化根节点就可以开始工作。子节点的建立是在有更多的数据进入到哈希树中的时候建立的。因此可以说哈希树和其他树一样是一个动态结构。)

下面我们以随机的10个数的插入为例,来图解HashTree的插入过程,这个史上最清晰的图解,你一定能看的明白^_^

有读者可能有疑问,如果一直冲突下去怎么办?首先,若关键字是整型,我们的10层哈希树完全可以分辨出来它们,这是质数分辨算法决定的。

(我们其实也可以把所有的键-值节点放在哈希树的第10层叶节点处,这第10层的满节点数就包含了所有的整数个数,但是如果这样处理的话,所有的非叶子节点作为键-值节点的索引,这样使树结构庞大,浪费空间)

【这里没有说的太清楚,此图是以2开始的连续质数创建的,即:从上到下的层级中的每个节点中的子树个数为2、3、5、7、11、13、17、19、23、29。第一层中的每个节点的子树个数为2,第二层中的每个节点子树个数为5.。。。。

 【上图中的子树上的数字,是其父节点的子树指针数组的索引值】

查找

哈希树的节点查找过程和节点插入过程类似,就是对关键字用质数序列取余,根据余数确定下一节点的分叉路径,直到找到目标节点。

如上图,最小”哈希树(HashTree)在从4G个对象中找出所匹配的对象,比较次数不超过10次。也就是说:最多属于O(10)。在实际应用中,调整了质数的范围,使得比较次数一般不超过5次。也就是说:最多属于O(5)。因此可以根据自身需要在时间和空间上寻求一个平衡点。

删除

哈希树的节点删除过程也很简单,哈希树在删除的时候,并不做任何结构调整。

只是先查到到要删除的节点,然后把此节点的“占位标记”置为false即可(即表示此节点为空节点,但并不进行物理删除)。

优点

1、结构简单

从哈希树的结构来说,非常的简单。每层节点的子节点个数为连续的质数。子节点可以随时创建。因此哈希树的结构是动态的,也不像某些哈希算法那样需要长时间的初始化过程。哈希树也没有必要为不存在的关键字提前分配空间。

需要注意的是哈希树是一个单向增加的结构,即随着所需要存储的数据量增加而增大。即使数据量减少到原来的数量,但是哈希树的总节点数不会减少。这样做的目的是为了避免结构的调整带来的额外消耗。

2、查找迅速

从算法过程我们可以看出,对于整数,哈希树层级最多能增加到10。因此最多只需要十次取余和比较操作,就可以知道这个对象是否存在。这个在算法逻辑上决定了哈希树的优越性。

一般的树状结构,往往随着层次和层次中节点数的增加而导致更多的比较操作。操作次数可以说无法准确确定上限。而哈希树的查找次数和元素个数没有关系。如果元素的连续关键字总个数在计算机的整数(32bit)所能表达的最大范围内,那么比较次数就最多不会超过10次,通常低于这个数值。

3、结构不变

从删除算法中可以看出,哈希树在删除的时候,并不做任何结构调整。这个也是它的一个非常好的优点。常规树结构在增加元素和删除元素的时候都要做一定的结构调整,否则他们将可能退化为链表结构,而导致查找效率的降低。哈希树采取的是一种“见缝插针”的算法,从来不用担心退化的问题,也不必为优化结构而采取额外的操作,因此大大节约了操作时间。

缺点

1、非排序性

哈希树不支持排序,没有顺序特性。如果在此基础上不做任何改进的话并试图通过遍历来实现排序,那么操作效率将远远低于其他类型的数据结构。

HashTree【转】的更多相关文章

  1. HashTree(哈希树) ——和trie类似,只是将字符换成了质数,sphinx用到了???

    摘自:http://blog.csdn.net/yang_yulei/article/details/46337405 哈希树的理论基础 [质数分辨定理] 简单地说就是:n个不同的质数可以" ...

  2. 查找——图文翔解HashTree(哈希树)

    引 在各种数据结构(线性表.树等)中,记录在结构中的相对位置是随机的.因此在机构中查找记录的时须要进行一系列和keyword的比較.这一类的查找方法建立在"比較"的基础上.查找的效 ...

  3. 012-数据结构-树形结构-哈希树[hashtree]、字典树[trietree]、后缀树

    一.哈希树概述 1.1..其他树背景 二叉排序树,平衡二叉树,红黑树等二叉排序树.在大数据量时树高很深,我们不断向下找寻值时会比较很多次.二叉排序树自身是有顺序结构的,每个结点除最小结点和最大结点外都 ...

  4. 【读书笔记】2016.12.10 《构建高性能Web站点》

    本文地址 分享提纲: 1. 概述 2. 知识点 3. 待整理点 4. 参考文档 1. 概述 1.1)[该书信息] <构建高性能Web站点>: -- 百度百科 -- 本书目录: 第1章 绪论 ...

  5. 【转载】Java集合类Array、List、Map区别和联系

    Java集合类主要分为以下三类: 第一类:Array.Arrays第二类:Collection :List.Set第三类:Map :HashMap.HashTable 一.Array , Arrays ...

  6. Java的容器类Collection和Map

    一,概念 JAVA集合只能存放引用类型的的数据,不能存放基本数据类型. java的容器类一共有两种主要类型,Colllection和Map. 两者的区别是:Collection是单个元素,而Map是存 ...

  7. 2016 Multi-University Training Contest 1 J.Subway

    Subway Time Limit: 7000/3500 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)Total Su ...

  8. NoSQL数据库笔谈(转)

    NoSQL数据库笔谈 databases , appdir , node , paper颜开 , v0.2 , 2010.2 序 思想篇 CAP 最终一致性 变体 BASE 其他 I/O的五分钟法则 ...

  9. Jmeter使用入门

    修改时间 修改内容 修改人 2016.3.12 创建 刘永志 2016.6.18 完成 刘永志 Jmeter简介 Jmeter的基本概念 百度百科: Apache JMeter是Apache组织开发的 ...

随机推荐

  1. ZooKeeper和Curator相关经验总结

    一.关于ZooKeeper的watch用法,需要注意 详细说明如下: ZooKeeper Watches All of the read operations in ZooKeeper - getDa ...

  2. Django--Auth 模块

    1 Auth模块是什么 Auth模块是Django自带的用户认证模块: 我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统.此时我们需要实现包括用户注册.用户登录.用户认证.注销.修改密码 ...

  3. salesforce lightning零基础学习(五) 事件阶段(component events phase)

    上一篇介绍了lightning component events的简单介绍.此篇针对上一篇进行深入,主要讲的内容为component event中的阶段(Phase). 一. 阶段(Phase)的概念 ...

  4. quartz配置参数org.quartz.jobStore.misfireThreshold含义解释

    配置定时任务参数 quartz.properties文件时 需要配置jobStore的超过时间数 默认为60秒(这里单位为毫秒) org.quartz.jobStore.misfireThreshol ...

  5. ARM64 Linux kernel virtual address space

    墙外通道:http://thinkiii.blogspot.com/2014/02/arm64-linux-kernel-virtual-address-space.html Now let's ta ...

  6. 第一个Quartz程序 (二)

    1 我们使用maven项目 2 创建一个job类,在execute()方法里写上业务逻辑代码. 3 在另外一个类中创建触发器,调度器,并且绑定job. 首先在项目的pom.xml引入需要的jar包. ...

  7. 动手实现react Modal组件

    Modal组件 长话不多说,接下来让我们来动手实现一个react Modal组件. 我们先来看一下实际效果 Modal的布局 首先,让我们先思考下一个Modal组件的布局是怎么样的. 我们先拿一个基本 ...

  8. 记一次升级Tomcat

    总述     JDK都要出12了,而我们项目使用的jdk却仍然还停留在JDK1.6.为了追寻技术的发展的脚步,我这边准备将项目升级到JDK1.8.而作为一个web项目,我们的容器使用的是Tomcat. ...

  9. a 标签提交表单

    document.getElementById('ECS_FORMBUY').submit();

  10. 10.C++-构造函数初始化列表、类const成员、对象构造顺序、析构函数

    首先回忆下,以前学的const 单独使用const修饰变量时,是定义的常量,比如:const int i=1; 使用volatile const修饰变量时,定义的是只读变量 使用const & ...