在散列表和排序后的列表中找一个元素,哪个查找速度最快? 最简洁的答案是什么?

来自:    更新日期:早些时候
在散列表和排序后的列表中找一个元素,哪个查找速度最快?~

散列表又叫做哈希表

1. 引言
哈希表(Hash Table)的应用近两年才在NOI中出现,作为一种高效的数据结构,它正在竞赛中发挥着越来越重要的作用。
哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间;而代价仅仅是消耗比较多的内存。然而在当前可利用内存越来越多的情况下,用空间换时间的做法是值得的。另外,编码比较容易也是它的特点之一。
哈希表又叫做散列表,分为“开散列” 和“闭散列”。考虑到竞赛时多数人通常避免使用动态存储结构,本文中的“哈希表”仅指“闭散列”,关于其他方面读者可参阅其他书籍。
2. 基础操作
2.1 基本原理
我们使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数, 也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素“分类”,然后将这个元素存储在相应“类”所对应的地方。
但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了“冲突”,换句话说,就是把不同的元素分在了相同的“类”之中。后面我们将看到一种解决“冲突”的简便做法。
总的来说,“直接定址”与“解决冲突”是哈希表的两大特点。

2.2 函数构造
构造函数的常用方法(下面为了叙述简洁,设 h(k) 表示关键字为 k 的元素所对应的函数值):

a) 除余法:
选择一个适当的正整数 p ,令 h(k ) = k mod p
这里, p 如果选取的是比较大的素数,效果比较好。而且此法非常容易实现,因此是最常用的方法。
b) 数字选择法:
如果关键字的位数比较多,超过长整型范围而无法直接运算,可以选择其中数字分布比较均匀的若干位,所组成的新的值作为关键字或者直接作为函数值。

2.3 冲突处理
线性重新散列技术易于实现且可以较好的达到目的。令数组元素个数为 S ,则当 h(k) 已经存储了元素的时候,依次探查 (h(k)+i) mod S , i=1,2,3…… ,直到找到空的存储单元为止(或者从头到尾扫描一圈仍未发现空单元,这就是哈希表已经满了,发生了错误。当然这是可以通过扩大数组范围避免的)。

2.4 支持运算
哈希表支持的运算主要有:初始化(makenull)、哈希函数值的运算(h(x))、插入元素(insert)、查找元素(member)。
设插入的元素的关键字为 x ,A 为存储的数组。
初始化比较容易,例如
const empty=maxlongint; // 用非常大的整数代表这个位置没有存储元素
p=9997; // 表的大小
procedure makenull;
var i:integer;
begin
for i:=0 to p-1 do
A[i]:=empty;
End;

哈希函数值的运算根据函数的不同而变化,例如除余法的一个例子:
function h(x:longint):Integer;
begin
h:= x mod p;
end;

我们注意到,插入和查找首先都需要对这个元素定位,即如果这个元素若存在,它应该存储在什么位置,因此加入一个定位的函数 locate
function locate(x:longint):integer;
var orig,i:integer;
begin
orig:=h(x);
i:=0;
while (i<S)and(A[(orig+i)mod S]<>x)and(A[(orig+i)mod S]<>empty) do
inc(i);
//当这个循环停下来时,要么找到一个空的存储单元,要么找到这个元
//素存储的单元,要么表已经满了
locate:=(orig+i) mod S;
end;
插入元素
procedure insert(x:longint);
var posi:integer;
begin
posi:=locate(x); //定位函数的返回值
if A[posi]=empty then A[posi]:=x
else error; //error 即为发生了错误,当然这是可以避免的
end;

查找元素是否已经在表中
procedure member(x:longint):boolean;
var posi:integer;
begin
posi:=locate(x);
if A[posi]=x then member:=true
else member:=false;
end;

这些就是建立在哈希表上的常用基本运算。

下文提到的所有程序都能在附录中找到。
3. 效率对比
3.1简单的例子与实验
下面是一个比较简单的例子:
===================================================================
集合 ( Subset )
问题描述:
给定两个集合A、B,集合内的任一元素x满足1 ≤ x ≤ 109,并且每个集合的元素个数不大于104 个。我们希望求出A、B之间的关系。只需确定在B 中但是不在 A 中的元素的个数即可。


这个题目是根据 OIBH NOIP 2002 模拟赛 # 1 的第一题改编的。

分析:我们先不管A 与 B 的具体关系如何,注意到这个问题的本质就是对于给定的集合A ,确定B 中的元素是否在 A 中。所以,我们使用哈希表来处理。至于哈希函数,只要按照除余法就行了,由于故意扩大了原题的数据规模, H(x) = x mod 15889;
当然本题可以利用别的方法解决,所以选取了速度最快的快速排序+二分查找,让这两种方法作效率对比。
我们假定 |A|=|B| ,对于随机生成的数据,计算程序重复运行50次所用时间。
对比表格如下:

哈希表(sec) 快速排序+二分查找(sec)
复杂度 O(N) (只有忽略了冲突才是这个结果。当然实际情况会比这个大,但是重复的几率与哈希函数有关,不容易估计) O(N log N+ N) = O(N log N)
测试数据规模 —— ——
500 0.957 0.578
1000 1.101 0.825
2500 1.476 1.565
5000 2.145 2.820
7500 2.905 4.203
10000 3.740 5.579
13500 7.775 7.753
15000 27.550 8.673


对于数据的说明:在 Celeron566 下用 TP 测试,为了使时间的差距明显,让程序重复运了行50次。同时哈希表中的P= 15889 ,下标范围 0..15888 。由于快速排序不稳定,因此使用了随机数据。

3.2 对试验结果的分析:
注意到两个程序的用时并不像我们期望的那样,总是哈希表快。设哈希表的大小为 P .

首先,当规模比较小的时候(大约为a< 10% * P,这个数据仅仅是通过若干数据估记出来的,没有严格证明,下同),第二种方法比哈希表快。这是由于,虽然每次计算哈希函数用O(1) 的时间,但是这个系数比较大。例如这道题的 H(x)=x mod 15589 ,通过与做同样次数的加法相比较,测试发现系数 > 12 ,因为 mod 运算本身与快速排序的比较大小和交换元素运算相比,比较费时间。所以规模小的时候,O(N)(忽略冲突)的算法反而不如 O(NlogN)。这一点在更复杂的哈希函数上会体现的更明显,因为更复杂的函数系数会更大。
其次,当规模稍大 (大约为 15%*P < a < 85%*P) 的时候,很明显哈希表的效率高。这是因为冲突的次数较少。
再次,当规模再大 (大约为 90%*P < a < P )的时候,哈希表的效率大幅下降。这是因为冲突的次数大大提高了,为了解决冲突,程序不得不遍历一段都存储了元素的数组空间来寻找空位置。用白箱测试的方法统计,当规模为13500的时候,为了找空位置,线性重新散列平均做了150000 次运算;而当规模为15000 的时候,平均竟然高达2000000 次运算,某些数据甚至能达到4265833次。显然浪费这么多次运算来解决冲突是不合算的,解决这个问题可以扩大表的规模,或者使用“开散列”(尽管它是动态数据结构)。然而需要指出的是,冲突是不可避免的。

初步结论:
当数据规模接近哈希表上界或者下界的时候,哈希表完全不能够体现高效的特点,甚至还不如一般算法。但是如果规模在中央,它高效的特点可以充分体现。我们可以从图像直观的观察到这一点。
















试验表明当元素充满哈希表的 90% 的时候,效率就已经开始明显下降。这就给了我们提示:如果确定使用哈希表,应该尽量使数组开大(由于竞赛中可利用内存越来越多,大数组通常不是问题,当然也有少数情况例外),但对最太大的数组进行操作也比较费时间,需要找到一个平衡点。通常使它的容量至少是题目最大需求的 120% ,效果比较好(这个仅仅是经验,没有严格证明)。

4. 应用举例
4.1 应用的简单原则
什么时候适合应用哈希表呢?如果发现解决这个问题时经常要询问:“某个元素是否在已知集合中?”,也就是需要高效的数据存储和查找,则使用哈希表是最好不过的了!那么,在应用哈希表的过程中,值得注意的是什么呢?
哈希函数的设计很重要。一个不好的哈希函数,就是指造成很多冲突的情况,从前面的例子已经可以看出来,解决冲突会浪费掉大量时间,因此我们的目标就是尽力避免冲突。前面提到,在使用“除余法”的时候,h(k)=k mod p ,p 最好是一个大素数。这就是为了尽力避免冲突。为什么呢?假设 p=1000 ,则哈希函数分类的标准实际上就变成了按照末三位数分类,这样最多1000类,冲突会很多。一般地说,如果 p 的约数越多,那么冲突的几率就越大。
简单的证明:假设 p 是一个有较多约数的数,同时在数据中存在 q 满足 gcd(p,q)=d >1 ,即有 p=a*d , q=b*d, 则有 q mod p= q – p* [q div p] =q – p*[b div a] . ① 其中 [b div a ] 的取值范围是不会超过 [0,b] 的正整数。也就是说, [b div a] 的值只有 b+1 种可能,而 p 是一个预先确定的数。因此 ① 式的值就只有 b+1 种可能了。这样,虽然mod 运算之后的余数仍然在 [0,p-1] 内,但是它的取值仅限于 ① 可能取到的那些值。也就是说余数的分布变得不均匀了。容易看出, p 的约数越多,发生这种余数分布不均匀的情况就越频繁,冲突的几率越高。而素数的约数是最少的,因此我们选用大素数。记住“素数是我们的得力助手”。
另一方面,一味的追求低冲突率也不好。理论上,是可以设计出一个几乎完美,几乎没有冲突的函数的。然而,这样做显然不值得,因为这样的函数设计很浪费时间而且编码一定很复杂,与其花费这么大的精力去设计函数,还不如用一个虽然冲突多一些但是编码简单的函数。因此,函数还需要易于编码,即易于实现。
综上所述,设计一个好的哈希函数是很关键的。而“好”的标准,就是较低的冲突率和易于实现。
另外,使用哈希表并不是记住了前面的基本操作就能以不变应万变的。有的时候,需要按照题目的要求对哈希表的结构作一些改进。往往一些简单的改进就可以带来巨大的方便。
这些只是一般原则,真正遇到试题的时候实际情况千变万化,需要具体问题具体分析才行。

这里大多数问题的答案都没有对错之分!
你可以把我的这些问题作为引子,展开讨论。例如下面有个问题是使用静态方法或是单例的缘由。如果那个面试的就此展开长篇大论,那他很有可能是个聪明能干的家伙!如果他一脸茫然的看着你,发出这种声音,很明显这就是只狒狒了。同样,想知道一个数是不是2的乘方也有很多方法,不过要是面试的人想用mod运算符,嗯……你知道我的意思吧。(你不知道也没关系,来根香蕉?)
需求
你能给出一些非功能性(或者质量)需求的例子么?
如果客户需要高性能、使用极其方便而又高度安全,你会给他什么建议?
你能给出一些用来描述需求的不同技术么?它们各自适用于什么场景?
需求跟踪是什么意思?什么是向前追溯,什么是向后追溯?
你喜欢用什么工具跟踪需求?
你怎么看待需求变化?它是好是坏?给出你的理由。
你怎样研究需求,发现需求?有哪些资源可以用到?
你怎么给需求制定优先级?有哪些技术?
在需求过程中,用户、客户、开发人员各自的职责是什么?
你怎么对待不完整或是令人费解的需求?
功能设计
在功能设计中有哪些隐喻?给出几个成功的例子。
如果有些功能的执行时间很长,怎么能让用户感觉不到太长的等待?
如果用户必须要在一个很小的区域内,从一个常常的列表中选择多个条目,你会用什么控件?
有哪些方法可以保证数据项的完整?
建立系统原型有哪些技术?
应用程序怎样建立对用户行为的预期?给出一些例子。
如何入手设计一组数量庞大而又复杂的特性,你能举出一些设计思路吗?
有一个列表,其中有10个元素,每个元素都有20个字段可以编辑,你怎样设计这种情况?如果是1000个元素,每个元素有3个字段呢?
用不同的颜色对一段文本中的文字标记高亮,这种做法有什么问题?
Web环境和Windows环境各有些什么限制?
技术设计

什么是低耦合和高聚合?封装原则又是什么意思?
在Web应用中,你怎样避免几个人编辑同一段数据所造成的冲突?
你知道设计模式吗?你用过哪些设计模式?在什么场合下用的?
是否了解什么是无状态的业务层?长事务如何与之相适应?
在搭建一个架构,或是技术设计时,你用过几种图?
在N层架构中都有哪些层?它们各自的职责是什么?
有哪些方法可以确保架构中数据的正确和健壮?
面向对象设计和面向组件设计有哪些不同之处?
怎样在数据库中对用户授权、用户配置、权限管理这几项功能建模?
怎样按照等级制度给动物王国(包括各种物种和各自的行为)建模?
程序设计

你怎样保证你的代码可以处理各种错误事件?
解释一下什么是测试驱动开发,举出极限编程中的一些原则。
看别人代码的时候,你最关心什么地方?
什么时候使用抽象类,什么时候使用接口?
除了IDE以外,你还喜欢哪些必不可少的工具?
你怎么保证代码执行速度快,而又不出问题?
什么时候用多态,什么时候用委派?
什么时候使用带有静态成员的类,什么时候使用单例?
你在代码里面怎么提前处理需求的变化?给一些例子。
描述一下实现一段代码的过程,从需求到最终交付。
算法

怎样知道一个数字是不是2的乘方?怎样判断一个数是不是奇数?
怎样找出链表中间的元素?
怎样改变10,000个静态HTML页面中所有电话号码的格式?
举出一个你所用过的递归的例子。
在散列表和排序后的列表中找一个元素,哪个查找速度最快?
不管是书、杂志还是网络,你从中所学到的最后一点算法知识是什么?
怎样把字符串反转?你能不用临时的字符串么?
你愿意用什么类型的语言来编写复杂的算法?
有一个数组,里面是从1到1,000,000的整数,其中有一个数字出现了两次,你怎么找出那个重复的数字?
你知道“旅行商问题(Traveling Salesman Problem)”么?
数据结构

怎样在内存中实现伦敦地铁的结构?
怎样以最有效的方式在数据库中存储颜色值?
队列和堆栈区别是什么?
用堆或者栈存储数据的区别是什么?
怎样在数据库中存储N维向量?
你倾向于用哪种类型的语言编写复杂的数据结构?
21的二进制值是什么?十六制值呢?
不管是书、杂志还是网络,你从中所学到的最后一点数据结构的知识是什么?
怎样在XML文档中存储足球比赛结果(包括队伍和比分)?
有哪些文本格式可以保存Unicode字符?

散列表又叫做哈希表

1. 引言
哈希表(Hash Table)的应用近两年才在NOI中出现,作为一种高效的数据结构,它正在竞赛中发挥着越来越重要的作用。
哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间;而代价仅仅是消耗比较多的内存。然而在当前可利用内存越来越多的情况下,用空间换时间的做法是值得的。另外,编码比较容易也是它的特点之一。
哈希表又叫做散列表,分为“开散列” 和“闭散列”。考虑到竞赛时多数人通常避免使用动态存储结构,本文中的“哈希表”仅指“闭散列”,关于其他方面读者可参阅其他书籍。
2. 基础操作
2.1 基本原理
我们使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数, 也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素“分类”,然后将这个元素存储在相应“类”所对应的地方。
但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了“冲突”,换句话说,就是把不同的元素分在了相同的“类”之中。后面我们将看到一种解决“冲突”的简便做法。
总的来说,“直接定址”与“解决冲突”是哈希表的两大特点。

2.2 函数构造
构造函数的常用方法(下面为了叙述简洁,设 h(k) 表示关键字为 k 的元素所对应的函数值):

a) 除余法:
选择一个适当的正整数 p ,令 h(k ) = k mod p
这里, p 如果选取的是比较大的素数,效果比较好。而且此法非常容易实现,因此是最常用的方法。
b) 数字选择法:
如果关键字的位数比较多,超过长整型范围而无法直接运算,可以选择其中数字分布比较均匀的若干位,所组成的新的值作为关键字或者直接作为函数值。

2.3 冲突处理
线性重新散列技术易于实现且可以较好的达到目的。令数组元素个数为 S ,则当 h(k) 已经存储了元素的时候,依次探查 (h(k)+i) mod S , i=1,2,3…… ,直到找到空的存储单元为止(或者从头到尾扫描一圈仍未发现空单元,这就是哈希表已经满了,发生了错误。当然这是可以通过扩大数组范围避免的)。

2.4 支持运算
哈希表支持的运算主要有:初始化(makenull)、哈希函数值的运算(h(x))、插入元素(insert)、查找元素(member)。
设插入的元素的关键字为 x ,A 为存储的数组。
初始化比较容易,例如
const empty=maxlongint; // 用非常大的整数代表这个位置没有存储元素
p=9997; // 表的大小
procedure makenull;
var i:integer;
begin
for i:=0 to p-1 do
A[i]:=empty;
End;

哈希函数值的运算根据函数的不同而变化,例如除余法的一个例子:
function h(x:longint):Integer;
begin
h:= x mod p;
end;

我们注意到,插入和查找首先都需要对这个元素定位,即如果这个元素若存在,它应该存储在什么位置,因此加入一个定位的函数 locate
function locate(x:longint):integer;
var orig,i:integer;
begin
orig:=h(x);
i:=0;
while (i<S)and(A[(orig+i)mod S]<>x)and(A[(orig+i)mod S]<>empty) do
inc(i);
//当这个循环停下来时,要么找到一个空的存储单元,要么找到这个元
//素存储的单元,要么表已经满了
locate:=(orig+i) mod S;
end;
插入元素
procedure insert(x:longint);
var posi:integer;
begin
posi:=locate(x); //定位函数的返回值
if A[posi]=empty then A[posi]:=x
else error; //error 即为发生了错误,当然这是可以避免的
end;

查找元素是否已经在表中
procedure member(x:longint):boolean;
var posi:integer;
begin
posi:=locate(x);
if A[posi]=x then member:=true
else member:=false;
end;

这些就是建立在哈希表上的常用基本运算。

下文提到的所有程序都能在附录中找到。
3. 效率对比
3.1简单的例子与实验
下面是一个比较简单的例子:
===================================================================
集合 ( Subset )
问题描述:
给定两个集合A、B,集合内的任一元素x满足1 ≤ x ≤ 109,并且每个集合的元素个数不大于104 个。我们希望求出A、B之间的关系。只需确定在B 中但是不在 A 中的元素的个数即可。

这个题目是根据 OIBH NOIP 2002 模拟赛 # 1 的第一题改编的。

分析:我们先不管A 与 B 的具体关系如何,注意到这个问题的本质就是对于给定的集合A ,确定B 中的元素是否在 A 中。所以,我们使用哈希表来处理。至于哈希函数,只要按照除余法就行了,由于故意扩大了原题的数据规模, H(x) = x mod 15889;
当然本题可以利用别的方法解决,所以选取了速度最快的快速排序+二分查找,让这两种方法作效率对比。
我们假定 |A|=|B| ,对于随机生成的数据,计算程序重复运行50次所用时间。
对比表格如下:

哈希表(sec) 快速排序+二分查找(sec)
复杂度 O(N) (只有忽略了冲突才是这个结果。当然实际情况会比这个大,但是重复的几率与哈希函数有关,不容易估计) O(N log N+ N) = O(N log N)
测试数据规模 —— ——
500 0.957 0.578
1000 1.101 0.825
2500 1.476 1.565
5000 2.145 2.820
7500 2.905 4.203
10000 3.740 5.579
13500 7.775 7.753
15000 27.550 8.673

对于数据的说明:在 Celeron566 下用 TP 测试,为了使时间的差距明显,让程序重复运了行50次。同时哈希表中的P= 15889 ,下标范围 0..15888 。由于快速排序不稳定,因此使用了随机数据。

3.2 对试验结果的分析:
注意到两个程序的用时并不像我们期望的那样,总是哈希表快。设哈希表的大小为 P .

首先,当规模比较小的时候(大约为a< 10% * P,这个数据仅仅是通过若干数据估记出来的,没有严格证明,下同),第二种方法比哈希表快。这是由于,虽然每次计算哈希函数用O(1) 的时间,但是这个系数比较大。例如这道题的 H(x)=x mod 15589 ,通过与做同样次数的加法相比较,测试发现系数 > 12 ,因为 mod 运算本身与快速排序的比较大小和交换元素运算相比,比较费时间。所以规模小的时候,O(N)(忽略冲突)的算法反而不如 O(NlogN)。这一点在更复杂的哈希函数上会体现的更明显,因为更复杂的函数系数会更大。
其次,当规模稍大 (大约为 15%*P < a < 85%*P) 的时候,很明显哈希表的效率高。这是因为冲突的次数较少。
再次,当规模再大 (大约为 90%*P < a < P )的时候,哈希表的效率大幅下降。这是因为冲突的次数大大提高了,为了解决冲突,程序不得不遍历一段都存储了元素的数组空间来寻找空位置。用白箱测试的方法统计,当规模为13500的时候,为了找空位置,线性重新散列平均做了150000 次运算;而当规模为15000 的时候,平均竟然高达2000000 次运算,某些数据甚至能达到4265833次。显然浪费这么多次运算来解决冲突是不合算的,解决这个问题可以扩大表的规模,或者使用“开散列”(尽管它是动态数据结构)。然而需要指出的是,冲突是不可避免的。

初步结论:
当数据规模接近哈希表上界或者下界的时候,哈希表完全不能够体现高效的特点,甚至还不如一般算法。但是如果规模在中央,它高效的特点可以充分体现。我们可以从图像直观的观察到这一点。

试验表明当元素充满哈希表的 90% 的时候,效率就已经开始明显下降。这就给了我们提示:如果确定使用哈希表,应该尽量使数组开大(由于竞赛中可利用内存越来越多,大数组通常不是问题,当然也有少数情况例外),但对最太大的数组进行操作也比较费时间,需要找到一个平衡点。通常使它的容量至少是题目最大需求的 120% ,效果比较好(这个仅仅是经验,没有严格证明)。

4. 应用举例
4.1 应用的简单原则
什么时候适合应用哈希表呢?如果发现解决这个问题时经常要询问:“某个元素是否在已知集合中?”,也就是需要高效的数据存储和查找,则使用哈希表是最好不过的了!那么,在应用哈希表的过程中,值得注意的是什么呢?
哈希函数的设计很重要。一个不好的哈希函数,就是指造成很多冲突的情况,从前面的例子已经可以看出来,解决冲突会浪费掉大量时间,因此我们的目标就是尽力避免冲突。前面提到,在使用“除余法”的时候,h(k)=k mod p ,p 最好是一个大素数。这就是为了尽力避免冲突。为什么呢?假设 p=1000 ,则哈希函数分类的标准实际上就变成了按照末三位数分类,这样最多1000类,冲突会很多。一般地说,如果 p 的约数越多,那么冲突的几率就越大。
简单的证明:假设 p 是一个有较多约数的数,同时在数据中存在 q 满足 gcd(p,q)=d >1 ,即有 p=a*d , q=b*d, 则有 q mod p= q – p* [q div p] =q – p*[b div a] . ① 其中 [b div a ] 的取值范围是不会超过 [0,b] 的正整数。也就是说, [b div a] 的值只有 b+1 种可能,而 p 是一个预先确定的数。因此 ① 式的值就只有 b+1 种可能了。这样,虽然mod 运算之后的余数仍然在 [0,p-1] 内,但是它的取值仅限于 ① 可能取到的那些值。也就是说余数的分布变得不均匀了。容易看出, p 的约数越多,发生这种余数分布不均匀的情况就越频繁,冲突的几率越高。而素数的约数是最少的,因此我们选用大素数。记住“素数是我们的得力助手”。
另一方面,一味的追求低冲突率也不好。理论上,是可以设计出一个几乎完美,几乎没有冲突的函数的。然而,这样做显然不值得,因为这样的函数设计很浪费时间而且编码一定很复杂,与其花费这么大的精力去设计函数,还不如用一个虽然冲突多一些但是编码简单的函数。因此,函数还需要易于编码,即易于实现。
综上所述,设计一个好的哈希函数是很关键的。而“好”的标准,就是较低的冲突率和易于实现。
另外,使用哈希表并不是记住了前面的基本操作就能以不变应万变的。有的时候,需要按照题目的要求对哈希表的结构作一些改进。往往一些简单的改进就可以带来巨大的方便。
这些只是一般原则,真正遇到试题的时候实际情况千变万化,需要具体问题具体分析才行。


在散列表和排序后的列表中找一个元素,哪个查找速度最快? 最简洁的答案是什么?视频

相关评论:
  • 15196501242在散列表和排序后的列表中找一个元素,哪个查找速度最快? 最简洁的答案...
    蓝英诞,直到找到空的存储单元为止(或者从头到尾扫描一圈仍未发现空单元,这就是哈希表已经满了,发生了错误。当然这是可以通过扩大数组范围避免的)。2.4 支持运算哈希表支持的运算主要有:初始化(makenull)、哈希函数值的运算(h(x))、插入元素(insert)、查找元素(member)。设插入的元素的关键字为 x ,A 为存储的数组。...

  • 15196501242软件开发专业的常识性问题
    蓝英诞描述一下实现一段代码的过程,从需求到最终交付。算法 怎样知道一个数字是不是2的乘方?怎样判断一个数是不是奇数?怎样找出链表中间的元素?怎样改变10,000个静态HTML页面中所有电话号码的格式?举出一个你所用过的递归的例子。在散列表和排序后的列表中找一个元素,哪个查找速度最快?不管是书、杂志...

  • 15196501242已知有序数组a前10000个元素是随机整数,现需查找某个整数是否在该数组中...
    蓝英诞【答案】:D 本题考查常见查找算法时间复杂度。顺序表查找:最好 O(1) 最坏 O(n) 最终 O(n)折半查找:最终logn二叉排序树:最终logn平衡二叉树:logn哈希表法(散列表):O(1),但是构建哈希表需要O(n)分块查找:O(logn)

  • 15196501242几种常见的查找算法之比较
    蓝英诞如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。时间复杂度:O(logn)三、哈希表(散列表)条件:先创建哈希表(散列表)原理:根据键值方式(Ke...

  • 15196501242计算机基础最全自学指南!
    蓝英诞散列表(Hash table):散列表源自于散列函数(Hash function),其思想是如果在结构中存在关键字和T相等的记录,那么必定在F(T)的存储位置可以找到该记录,这样就可以不用进行比较操作而直接取得所查记录。 堆(Heap):堆是一种特殊的树形数据结构,一般讨论的堆都是二叉堆。 树(Tree):树是典型的非线性结构,它是包括,...

  • 15196501242查找- 散列技术 - 散列表的概念
    蓝英诞散列方法是使用函数h将U映射到表T[ m ]的下标上(m=O(|U|)) 这样以U中关键字为自变量 以h为函数的运算结果就是相 应结点的存储地址 从而达到在O( )时间内就可完成查找 其中 ① h U→{ … m } 通常称h为散列函数(Hash Function) 散列函数h的作用是压缩待处理的下标范围 使待处理 的|U...

  • 15196501242从无序表中顺序取出一个元素并插入适当位置这种排序方法叫什么?_百度...
    蓝英诞插入法排序 组成;开始时有序表只有1个元素,无序表包含n-1个元素,排序过程是每次从无序数组中抽取一个元素,把它插到有序数组中的适当位置,最后形成新的有序表

  • 15196501242是的 计算机算法
    蓝英诞HashFunction是一种从任何一种数据中创建小的数字“指纹”的方法。该函数将数据打乱混合,重新创建一个叫做散列值的指纹。散列值通常用来代表一个短的随机字母和数字组成的字符串。好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理中,不抑制冲突来区别数据,会使得数据库记录更难找到。堆排序 ...

  • 15196501242<算法图解>
    蓝英诞合并排序:o(nlogn),快速排序:o(nlogn):层数o(logn)乘每层需要的时间o(n),但最差情况为o(n方)。散列表-基本数据结构之一:内部机制:实现、冲突、散列函数。散列表无序,数据结构:数组、列表、(栈、不能用于查找)、散列表(包含额外逻辑)。数组和链表都直接映射到内存,但散列表使用散列...

  • 15196501242哈希索引和倒排查找怎么用简单
    蓝英诞查找第k小元素即在n个元素中(未排序)找到第k小的元素。方法同快速排序,采用递归方式。程序如下:program kspv;const n=7;type arr=array[1..n] of integer;var b:arr;i,k:integer;function p(s,t:integer):integer;var i,j,t1,x:integer;begin i:=s;j:=t;x:=b[i];repeat while...

  • 相关主题精彩

    版权声明:本网站为非赢利性站点,内容来自于网络投稿和网络,若有相关事宜,请联系管理员

    Copyright © 喜物网