admin 管理员组

文章数量: 1086019


2024年12月26日发(作者:create table语法)

哈希树(HashTree)

罗堃 吴朝宏

从2000年开始,作者开始研究基于TCP/IP的短信息传输技术。这种技术目

前在国际上的标准被成为SMPP(Short Message Peer to Peer Protocol)。SMPP协

议是一种支持异步传输模式(Asynchronized Transmission Mode)的信息传输方

式。这种异步方式主要体现在两个地方:传递信息和等待确认。在为电信运营商

编写软件的过程当中,解决大容量(百万用户以上)要求下的快速查找与匹配成

为实现这个系统的核心任务。

作者在反复设计整个过程中曾经尝试过很多种方式和算法,但都未能取得满

意的效果。最终不得不自己开始设计针对这种系统的特殊存储模式。并在这个过

程中,找到了一种比较理想的数据存储结构——哈希树(HashTree)。

一、查找算法

在各种数据结构(线性表、树等)中,记录在结构中的相对位置是随机的,

和记录的关键字之间不存在确定的关系。因此在机构中查找记录的时需要进行一

系列和关键字的比较。这一类的查找方法建立在“比较”的基础上。在顺序查找

时,比较的结果为“

”与“

”两种可能。在折半查找、二叉排序树查找和

B

树查找时,比较的结果为“

”、“

”与“

”三种可能。查找的效率依赖于查

找过程中所进行的比较次数。

为确定记录在查找表中的位置,需和给定值进行比较的关键字个数的期望值

成为查找算法在查找成功时的平均查找长度(Average Search Length)。下面我们

简单讨论一下在含有

n

个数据记录的结构中,各种查找算法的平均查找长度。

在等概率的情况下,顺序查找的平均查找长度为:

ASL

ss

n1

2

对于折半查找(表要求是顺序表),在

n

比较大(

n50

)的时候,可有以下

近似结果:

ASL

bs

log

2

(n1)1

在随机情况下(二叉树查找可能退化为顺序查找),对于二叉树,其平均查

找长度为:

ASL

b

14logn

在平衡二叉树上进行查找,其平均查找长度为:

ASL

bb

log

(5(n1))2

,其中

15

2

对于一颗

m

阶的

B

树,从根节点到关键字所在节点的路径上涉及的节点数

n1

)1

2

可以说是平均查找长度的上限:

ASL

B

log

m/2

(

总的来说,这些查找算法的效率都将随着数据记录数的增长而下降。仅仅是

有的比较快(时间复杂度为

O(n)

),有的比较慢(时间复杂度是

O(logn)

)而已。

这些查找算法的平均查找长度是在一种比较理想的情况下获得的。在实际应用当

中,对数据结构中数据的频繁增加和删除将不断地改变着数据的结构。这些操作

将可能导致某些数据结构退化为链表结构,那么其性能必然将下降。为了避免出

现这种情况而采取的调整措施,又不可避免的增加了程序的复杂程度以及操作的

额外时间。

二、哈希表

理想的情况是希望不经过任何比较,一次存取便能得到所查的记录,那就必

须在记的存储位置和它的关键字之间建立一个确定的对应关系

f

,使每个关键字

和结构中一个唯一的存储位置相对应。因而在查找时,只要根据这个对应关系

f

找到给定值

K

的像

f(K)

。若结构中存在关键字和

K

相等的记录,则必定在

f(K)

的存储位置上,由此,不需要进行比较便可直接取得所查记录。在此,我们称这

个对应关系为哈希(Hash)函数,按这个思想建立的表为哈希表。

在哈希表中对于不同的关键字可能得到同一哈希地址,即

K

1

K

2

,而

。在一般情况下,冲突只能尽可

f(K

1

)f(K

2

)

。这种现象称做冲突(Collision)

能地减少,而不能完全避免。因为哈希函数是从关键字集合到地址集合的映像。

通常关键字的集合比较大,它的元素包括所有可能的关键字,而地址集合的元素

仅为哈希表中的地址值。

假设标识符可定义为以字符为首的8位字符,则关键字(标识符)的集合大

17

小为

C

26

C

36

7!1.0933810

12

,而在一个源程序中出现的数据对象是有限的,

设有10000个元素。地址集合中的元素为0到9999。因此在一般情况下,哈希

函数是一个压缩映像函数,这就不可避免的要产生冲突。

二、哈希树的理论基础

2.1质数分辨定理

定理1

选取任意

n

个互不相同的质数:

P

1

P

2

P

3

P

n-1

P

n

nN

),定义:

M

P

i

P

1

P

2

P

3

P

n

i1

n

mk

1

k

2

mM

m,k

1

,k

2

N

),那么对于任意的

i

1,n

,(

k

1

dmoP

i

=(

k

2

modP

i

)不可能总成立。

证明

:假设定理1的结论不正确,那么对于任意的

i

1,n

,(

k

1

modP

i

)=

k

2

modP

i

)将总是成立。这个可以表达为:

k

1

s

1i

P

i

r

i

,k

2

s

2i

P

i

r

i

,其中s

1i

,s

2i

,r

i

N

设:

Kk

2

k

1

s

2i

s

1i

P

i

0

显然,

K

是一个合数,而且包含质因素

P

i

。根据质数的定义和质因素分解定

理,

K

可以表达为:

KPP

n1

P

n

SM,其中SN

1

P

2

P

3



而另外一方面,根据

mk

1

k

2

mM

,可以得出:

0k

2

k

1

M

很明显,这两个结论是相互矛盾的。因此原定理1正确。

这个定理可以简单的表述为:

n

个不同的质数可以“分辨”的连续整数的个

数和他们的乘积相等。“分辨”就是指这些连续的整数不可能有完全相同的余数

序列。这个为哈希树的分辨方式奠定了理论基础。

显然,这个定理的一个特殊情况就是

P

n

(nN)

为从2起的连续质数。我们可

以记

M

n

为前

n

个连续质数的乘积。连续10个质数就可以分辨大约

个数,已经超过计算机中常用整数(32bit)的表达范围。连续

M

10

6469693230

100个质数就可以分辨大约

M

100

4.7119310

219

而按照目前的CPU水平,100次取余的整数除法操作几乎不算什么难事。在

实际应用中,整体的操作速度往往取决于节点将关键字装载内存的次数和时间。

一般来说,装载的时间是由关键字的大小和硬件来决定的;在相同类型关键字和

相同硬件条件下,实际的整体操作时间就主要取决于装载的次数。他们之间是一

个成正比的关系。

2.2余数分辨定理

在这里,我们对更为普遍的余数分辨方式做一个讨论。

定理2

选取任意

n

个互不相同的自然数:

I

1

I

2

I

3

I

n-1

I

n

nN

),定义LCM(Lease Common Multiple)为

I

1

,I

2

,I

3

,,I

n-1

,I

n

的最

小公倍数。

mk

1

k

2

mLCM

m,k

1

,k

2

N

),那么对于任意的

i

1,n

k

1

modI

i

)=(

k

2

modI

i

)不可能总成立。

证明

:假设定理2的结论不正确,那么对于任意的

i

1,n

,(

k

1

modI

i

)=

k

2

modI

i

)将总是成立。这个可以表达为:

k

1

s

1i

I

i

r

i

,k

2

s

2i

I

i

r

i

,其中s

1i

,s

2i

,r

i

N

设:

Kk

2

k

1

s

2i

s

1i

I

i

0

显然,

K

是一个合数,而且包含因素

I

i

。根据最小公倍数的定义,

K

可以表

达为:

Kk

2

k

1

SLCMLCM,其中SN

而另外一方面,根据

mk

1

k

2

mLCM

,可以得出:

0k

2

k

1

LCM

很明显,这两个结论是相互矛盾的。因此原定理2正确。

这个定理2可以简单的表述为:

n

个不同的数可以“分辨”的连续整数的个

数不超过他们的最小公倍数。超过这个范围就意味着冲突的概率会增加。定理1

是定理2的一个特例。

2.3分辨算法评价标准

1. 状态和特征

分辨也即分辨不同的状态。分辨一般是先定义一组不同的状态,然后将这些

状态记录下来形成特征。由这些特征所形成的空间是特征空间。特征空间的纬数

是特征数列的长度。

2. 分辨能力

分辨能力D,也称分辨范围,就是指分辨算法可以分辨的最大连续空间大小。

在这个范围内,分辨算法可以唯一确定被分辨数。即任何两个在分辨范围内的数,

不可能具有完全相同的特征数。这些特征数会以某种形式被记录下来,或者以数

据结构的形式体现出来。

对于两个具有相同分辨能力的分辨算法,我们认为他们是等价算法。

D

的时候,分辨算法的分辨能力最强。但是同时分辨算法所使用的特

征空间也将是无穷大,我们不可能有足够的空间来存放这些相关特征记录。

3. 冲突概率

当被分辨数之间的距离超出分辨范围的时候,就有可能发生冲突,这种冲突

的概率被定义为冲突概率

0

1

1

D

当被分辨的数是随机分布在整个数轴的时候,两个数之间的距离可能会超过

分辨范围。这个时候分辨算法不一定会完全失效。冲突概率

就体现为衡量某种

分辨算法在处理散列时候的特性。冲突概率越小,那么处理散列的能力就越强,

对非连续空间的特征分辨能力就越高;反之,那么处理散列的能力就越弱,对非

连续空间的特征分辨能力就越低。

对于两个具有相同冲突概率的分辨算法,我们也认为他们是等价算法。

4. 分辨效率

根据分辨算法的特点,我们可以定义分辨效率

0%

分辨能力LCM

100%=100%100%

所有用于分辨的特征个数的乘积M

在由定理1和定理2可以得知:当用于余数分辨的整数数列是某一个确定整

数,或者互不相等的质数数列的时候,或者是互相没有公约数的整数数列,分辨

效率

100%

;其他情况下,分辨效率都小于100%。

5. 简化度

在分辨算法中,我们定义数列的简化度

为:

0

1

1

所有用于分辨的特征个数的和

所有用于分辨的特征个数的和,代表了分辨所需要的特征总数。特征总数越

少,那么简化度就越高。分辨算法的简化度越高,说明所用状态数越少,分辨过

程将能更简单。这个评价标准有利于我们去除冗余特征的部分。

定理3

:在相同分辨范围条件下的余数分辨算法中,所有分辨效率

100%

的分辨数列的简化度都小于分辨效率

=100%

的分辨数列的简化度。

证明:分辨效率

100%

意味着用于分辨的整数数列中,肯定存在某两个整

100%

)数有约数(否则他们的乘积就是他们的最小公倍数,那么分辨效率

这个约数我们称它为这两个数之间的最大公约数GCD(Greatest Common

Divisor)。不妨设这两个整数为:

I

1

Q

1

GCD,I

2

Q

2

GCD,其中Q

1

1,Q

2

1,GCD1

显然如果用

Q

1

代替

I

1

,将不会影响这些整数之间的最小公倍数LCM。一方

面,这些整数的积得到了减少,分辨效率将有所提高;另外一方面,这些整数的

和得到了减少,简化度也因此而提到。通过反复简化操作这个数列的分辨效率总

可以达到100%1:分辨效率

100%

的分辨数列总可以简化,而且可以简化成

分辨效率

=100%

的分辨数列。

6. 综合评价指标

我们定义分辨算法综合评价指标

=

D

特征空间纬数

这个标准意味着:分辨范围越大,简化度越高,那么这个算法的综合性能就

越好。例如:在余数分辨法中,如果我们选择数列

100,200,300

作为分辨数列,

那么采取这个数列的余数分辨算法各项指标如下:

DLCM600

1

0.001667

600

LCM

0.00001

100200300

1

0.001667

100200300

D

0.33333

3

作为分辨数列,那么采取这个数列的余数分辨

3,5,7,11

如果我们选择数列

2,

算法各项指标如下:

DLCM2310

1

4.3210

4

2310

100%

1

0.03571

235711

D

16.5

5

显然后者在综合评价方面要优于前者。

2.4线性分辨算法

线性分辨算法是利用线性函数

f(x)axb

作为分辨算法的分辨算法,或者

称为余数分辨算法。这种算法简单易行。上面所有的讨论都是基于线性分辨算法

的。

2.5非线性分辨算法

非线性分辨算法是指在特征数的获取过程中采用非线性函数的方法。这也就

是说,可以完全使用非线性函数,或者非线性函数和线性函数混合使用。但是只

要是使用了非线性函数,那么就被称作非线性分辨。

在这些算法里面,可以分成两类:非线性函数特征法和分段特征提取法。

1. 非线性函数特征法

利用单值非线性函数(例如:

f(x)x,f(x)x

n

)来提取特征的方法就称

为非线性特征法。很明显这些函数的单值对应性,并没有改变分辨范围的大小。

这些函数仅仅是将特征空间做了一个变形处理。这种变形可能是平移、缩放等等。

但是分辨能力没有扩大,冲突概率也并没有得到减少,简化度也没有发生变化,

分辨算法的整体评价标准也没有改变。

2. 分段特征提取法

分段特征提取法就是将被分辨的数分成若干部分,每个部分有若干状态。假

设某个数可以被分成

n

段,每段所有可能的状态个数为

S

i

(其中

i

1,n

)。那

么我们可以得出以下结论:

S

i

N,且S

i

2

任何一个段的状态至少是有两个状态,否则这种分段是毫无意义的。或者说

这段是没有任何特征的,对分辨算法来说是一种时间和空间的浪费。

这种算法的分辨能力是:

D

S

i

S

1

S

2

S

3

S

n

2

n

i1

n

其冲突概率:

1

2

n

D

100%

,简化度为: 这种算法的分辨效率

1

S

i

i0

n

1

S

1

S

2

S

3

S

n

1

2n

总体评价:

D



n

S

i0

n

i0

n

i

n

S

i

1

S

1

S

2

S

3

S

n

nS

1

S

2

S

3

S

n

这种算法可以做到在所有分辨算法中的简化度最高,综合评价

也可以很

高。但这种分辨算法的综合评价

并不总是最高的。

例如:当我们在分辨32位整数的时候,使用这种分段特征分辨法,那么这

种分辨方法的各项指标如下:

D2

32

=4294967276

1

2.3310

10

D

100%

1

0.015625

232

D

2097152

32

如果在余数分辨算法中,我们选择从2到29的连续10个质数作为分辨数列,

那么这个分辨方法的各项指标如下:

DM

10

6469693230

1

1.5410

10

D

100%

11

0.00775

2357111317192329129

D

5015226.07

10

在分辨以2进制为基础的连续整数的时候,这种算法有着明显的优势。但是

在分辨散列的时候,例如:以字符为基础的字符串的时候,其特征纬数和者特征

数将会迅速增加。过多的特征纬数和特征数意味者,对于特征空间的浪费、更多

的初始化时间以及更多比较次数和比较时间。为了仍然能够使用这种分辨方法,

普遍采用对字符串压缩编码转换成整数后,再使用该分辨算法。但是即使是采取

转换手段,对于超长的字符串仍然不是十分容易处理。

三、哈希树的组织结构

使用不同的分辨算法都可以组织成哈希树。一般来说:每层哈希树的节点下

的子节点数是和分辨数列一致的。哈希树的最大深度和特征空间的纬数是一致

的。

为了研究的方便,我们选择质数分辨算法来建立一颗哈希树。选择从2开始

的连续质数来建立一个十层的哈希树(因为

M

10

已经超过了计算机中常用整数的

表达范围)。第一层节点为根节点,根节点下有2个节点;第二层的每个节点下

有3个节点;依此类推,即每层节点的子节点数目为连续的质数。到了第十层,

每个节点下有29个节点。

图1 哈希树的组织结构

同一节点中的子节点,从左到右代表不同的余数结果。例如:第二层节点下

有三个子节点。那么从左到右分别代表:除3余0,除3余1和除3余2。

对质数的余数决定了处理的路径。例如:某个数来到第二层子节点,那么它

要做取余操作,然后再决定从哪个子节点向下搜寻。如果这个数是12那么它需

要从第一个子节点向下搜索;如果这个数是7那么它需要从第二个子节点向下搜

索;如果这个数是32那么它需要从第三个子节点向下搜索。

如果所有的节点都存在,并容纳数据的话,那么可以容纳的数据项目总数将

M

10

要大一些:

M(10)

M

i

6.70302888910

9

i1

10

如果在建立当初就建立所有的节点,那么所消耗的计算时间和磁盘空间是巨

大的。在实际使用当中,只需要初始化根节点就可以开始工作。子节点的建立是

在有更多的数据进入到哈希树中的时候建立的。因此可以说哈希树和其他树一样

是一个动态结构。

这些节点有以下基本元素:

1. 节点的关键字

我们用key来表示节点的关键字。在整个树中这个数值是唯一的。当节

点占据标志位(occupied)为真的时候,关键字才认为是有效的,否则是无

效的。

2. 节点是否被占据

我们用occupied来表示节点是否被占据。如果节点的关键字(key)有效,

那么occupied应该设置位true,否则设置为false。

3. 节点的子节点数组

我们用nodes[i]来表示节点的第i个子节点的地址。第10个质数为29,

余数不可能大于32。这个数组的固定长度为可以设置为32,即

0i31

当第i个子节点不存在时,nodes[i]=NULL,否则为子节点的地址。

4. 节点的数据对象

我们用value来表示节点的数据对象。

四、插入规则

设需要插入的关键字和数据分别为key和value。用level表示插入操作进行

到第几层,level从0开始。Prime表为连续质数表。插入过程从根节点开始执行:

1. 如果当前节点没有被占据(occupied = false),则设置该节点的key和

value,并设置占据标记为true,然后返回。

2. 如果当前节点被占据(occupied = true),则计算index = key mod

Prime[level]。

3. 如果nodes[index] = NULL,那么创建子节点,将level加1,并重复第1

步操作。

4.

如果nodes[index]为一个子节点那么,将level加1,然后重复第1步操

作。

用伪码来表示如下:

void insert (HashNode entry, int level, Key key, Value value)

{

if (ed = false)

{

}

= key;

= value;

ed = true;

return;

}

int index = key mod Prime[level];

if( nodes[index] = NULL)

{

}

nodes[index] = new HashNode();

level = level + 1;

insert(nodes[index], level, key, value);

五、查找操作

设需要查找的关键字为key。用level表示插入进行到第几层,level从0开始。

Prime表为连续质数表。插入过程从根节点开始执行:

1. 如果当前节点没有被占据(occupied = false),则执行第5步操作。

2. 如果当前节点已经被占据(occupied = true),则读取该节点的关键字,

将它和key进行比较。

3. 如果相等,则返回查找成功和该节点的value。

4. 如果不等,则执行第5步操作。

5. 计算index = key mod Prime[level]。

6. 如果nodes[index] = NULL,那么则返回查找失败。

7. 如果nodes[index]为一个子节点那么,将level加1,然后重复第1步操

作。

用伪码来表示如下:

Boolean search(HashNode entry, int level, Key key, Value value)

{

if(ed = true)

{

if( = key)

{

value = ;

return true;

}

}

}

level = level + 1;

return search(nodes[index], level, key, value);

int index = key mod Prime[level];

if( nodes[index] = NULL)

{

}

return false;

六、删除操作

设需要删除的关键字为key。用level表示插入进行到第几层,level从0开始。

Prime表为连续质数表。插入过程从根节点开始执行:

1. 如果当前节点没有被占据,则执行第5步操作。

2. 如果当前节点被占据,则读取该节点的关键字,将它和key进行比较。

3. 如果相等,则设置该节点的占用标记为false,并返回删除操作成功。

4. 如果不等,则执行第5步操作。

5. 计算index = key mod Prime[level]。

6. 如果nodes[index] = NULL,那么则返回删除失败。

7. 如果nodes[index]为一个子节点那么,将level加1,然后重复第1步操

作。

用伪码来表示如下:

Boolean remove(HashNode entry, int level, Key key)

{

if(ed = true)

{

if( = key)

{

ed = false;

return true;

}

}

}

level = level + 1;

return remove(nodes[index], level, key);

int index = key mod Prime[level];

if( nodes[index] = NULL)

{

}

return false;

七、哈希树的特点

7.1结构简单

从哈希树的结构来说,非常的简单。每层节点的子节点个数为连续的质数。

子节点可以随时创建。因此哈希树的结构是动态的,也不像某些哈希算法那样需

要长时间的初始化过程。哈希树也没有必要为不存在的关键字提前分配空间。

需要注意的是哈希树是一个单向增加的结构,即随着所需要存储的数据量增

加而增大。即使数据量减少到原来的数量,但是哈希树的总节点数不会减少。这

样做的目的是为了避免结构的调整带来的额外消耗。

7.2操作简单

从上面所讲述的操作过程来说是相当简单的。程序上特别容易实现,比起

B

树都更容易理解和实现。作者是通过实际中的应用,将数据和代码量减到了最低。

这种做法不仅提高了速度,而且编写起来更容易些。

7.3查找迅速

从算法过程我们可以看出,level最多能增加到10。因此最多只需要十次取

余和比较操作,就可以知道这个对象是否存在。这个在算法逻辑上决定了哈希树

的优越性。

一般的树状结构,往往随着层次和层次中节点数的增加而导致更多的比较操

作。操作次数可以说无法准确确定上限。而哈希树的查找次数和元素个数没有关

系。如果元素的连续关键字总个数在计算机的整数(32bit)所能表达的最大范围

内,那么比较次数就最多不会超过10次,通常低于这个数值。

7.4结构不变

从删除算法中可以看出,哈希树在删除的时候,并不做任何结构调整。这个

也是它的一个非常好的优点。常规树结构在增加元素和删除元素的时候都要做一

定的结构调整,否则他们将可能退化为链表结构,而导致查找效率的降低。哈希

树采取的是一种“见缝插针”的算法,从来不用担心退化的问题。也不必为优化

结构而采取额外的操作,因为大大节约了操作时间。

7.5非排序性

哈希树不支持排序,没有顺序特性。就好比你可以使用select * where id =

12345的SQL选择语句,但是不可以使用select * where id > 12345的SQL语句。

如果在此基础上不做任何改进的话并试图通过遍历来实现排序,那么操作效率将

远远低于其他类型的数据结构。

八、冲突处理

从定理1中我们可以知道,这个定理只保证了

M

10

个连续的整数是可以被“分

辨”的。如果在使用中,有两个整数之间的距离超过

M

10

,那么就可能会出现冲

突。

解决冲突的办法有两种:一种是主动解决这种冲突,另外一种是被动的。

所谓主动解决冲突,就是我们可以选择更大的质数序列来组织哈希树,只到

这些质数的乘积可以覆盖所有可能的范围。或者选择更多的质数,只到他们的乘

积可以覆盖所有可能的范围。

另外一个方面如果这种冲突发生的概率很低,那么可以让哈希树被动适应这

种变化。冲突对哈希树来说并非不可以被接纳。无非是增加了哈希树的层数,或

者说增加了所需要比较和取余的次数。但是为了容纳过多的冲突将会导致哈希树

的严重退化,最终将退化一个链表结构。

这些能产生冲突的数值之间的距离

Distance

必须满足:

DistanceSM

10

,其中SN

因此任何两个重复的数之间的距离必须为

M

10

,而非简单成倍关系。即

k

nk

nN,且nkM

10

不会导致哈希树的退化。

九、字符串关键字的处理

字符串是由字符组成,字符都具有某种具体的编码方式。由这种编码方式最

终都可以转换成字节数组的形式。因此我们可以简单的将字符串看作是256进制

的数。例如:“abc”可以转换成字节数组0x616263,可以看作是十进制的6382179。

那么一个20个字符的字符串将是一个很大的数。我们不能简单地使用计算

机的整数来做除法,而是使用程序模拟人工的除法方式来做除法并获得余数。一

个简单的例子如下:

int hash(String string, int prime)

{

byte[] bytes = es();

int residual = 0;

for(int i = ;i > 0;i --)

{

residual = residual * 256 + bytes[i – 1]

residual = residual mod prime

}

return residual;

}

虽然多做了几次基本的加、减、乘、除运算。但是因为都是整数计算,对于

目前的CPU水平来说,几乎不算什么难事。而且还可以通过移位运算代替乘法

运算,这样能更快些。而且除法的除数和被除数都不会太大,计算起来也不会有

太多的CPU消耗。

在这种情况下,数与数之间的距离,很容易超过

M

10

。如果两个字节数组之

间的长度差超过5个字节,就很有可能出现冲突现象。但是在长期实际应用当中,

这种情况并没有发生,这是为什么呢?

这种现象可以解释如下:从冲突的角度来严格考虑,这两个字符串之间的距

离必须满足

DistanceSM

10

的关系,即至少要相差5个字节。然而,其中一

个字符串的字节数组再加上这个距离后以后,很难完全保证整个字节数组能落在

一个可读并且有意义的字符编码范围内。例如:ASCII码的范围是0~127。在加

上一个数值后,可能会落到127到255的范围内,这个部分是不可见的ASCII

码。不可见的ASCII码,一般也不会作为关键字的。并且进入哈希树的字符串

关键字都还具有一定的实际意义。所以这种冲突可以说很难发生。

另外即使这种情况发生了,那么概率也是十分低的,哈希树本身在一定程度

上是可以容纳这种冲突存在的。所以直接使用字符串作为关键字并不影响的运算

结果。不过最好还是需要对字符串做更可靠的编码组织,以求完全防止这种冲突

的发生。例如:使用MD5和选用更大的质数相结合的办法。这样就可以使得通

过层次比较少的哈希树来获得对关键字区间的完整覆盖。这样就减少了比较操作

的次数,并提高整体的工作效率。

十、哈希树的退化问题

对于哈希树来说,可以退化成一个链表。但是这种严重退化是需要比较苛刻

的条件。在前面所讲述的哈希树例子当中,能使得该哈希树严重退化的一个数列

为:

1,M

1

,M

2

,M

3

,,M

10

当哈希树在没有任何元素的情况下,上述数列将使得哈希树变成一个链表。

但是任何干扰或者数列前后次序的变化都将打乱处理的顺序,而导致元素的均匀

分布。要使得某种实际应用情况下的哈希树产生退化,需要一个十分特殊的数列。

而这个数列出现的概率十分低,因此可以说在很大程度上哈希树是不会退化的。

十一、总结

从上面的分析可以看出哈希树是一种可以自适应的树。通过给出足够多的不

同质数,我们总可以将所有已经出现的关键字进行区别。而质数本身就是无穷无

尽的。这种方式使得关键字空间和地址空间不再是压缩对应方式,而是完全可以

等价的。

哈希树可以广泛应用于那些需要对大容量数据进行快速匹配操作的地方。例

如:数据库索引系统、短信息中的收条匹配、大量号码路由匹配、信息过滤匹配。

程序员可以利用各种代码来实现哈希树结构(作者已经做过C和Java的版本)。

它可以为程序员提供一种使用起来更加方便,更加简单的快速数据存储方式。


本文标签: 分辨 节点 算法 关键字 特征