1 前言

在讲散查找之前,我们先说一下为什么要使用散列查找
现在已知的几种查找方法:

查找方法 复杂度
顺序查找 O(N)
二分查找(静态查找) O(log2N)
插值查找
斐波那契查找 O(log2N)
二叉搜索树 O(h) h为树的高度
平衡二叉树 O(log2N)

用二叉搜索树查找前面在介绍 二叉排序树(BST)的创建,查找,插入,删除及最大最小结点的时候给出了详细的实现。
除了上面的查找算法,还有其他方法吗?

下面来看一个例子:在登录QQ的时候,QQ服务器是如何核对你的身份?面对庞大的用户群,如何快速找到用户信息?
分析:看看是否可以用二分法查找,解决下面三个问题。

  1. 十亿(109 ≈ 230)有效用户,用二分查找30次。√ 可行
  2. 十亿(109 ≈ 230) × 1K ≈ 1024G,1T连续空间(每个用户1k的空间储存信息)。√ 可行
  3. 按有效QQ号大小有序存储:在连续存储空间中,插入和删除一个新QQ号码将需要移动大量数据。× 不可行

我们这里是动态查找,需要不断的插入,删除元素,采用二分查找,每次插入删除都需要移动大量的元素,这样显然是不可行的。

现在问题来了,如何快速搜索到需要的关键词?如果关键词不方便比较怎么办?

  • 查找的本质: 已知对象找位置。

    • 有序安排对象:全序(二分查找)、半序(查找树)
    • 直接“算出”对象位置:散列直接“算出”对象位置:散列
  • 散列查找法的两项基本工作:

    • 计算位置:构造散列函数确定关键词存储位置;
    • 解决冲突:应用某种策略解决多个关键词位置相同的问题
  • 时间复杂度几乎是常量:O(1),即查找时间与问题规模无关!

以上通俗的说,就是数据太多了,整理起来太麻烦了,按照大小有序存放,组织成树都太麻烦了,工作量非常大。干脆不整理,给我一个关键字,直接带入公式,计算出关键字的位置,就找到想要的元素了,由于是用公式计算出关键字的存储位置,所以时间复杂度是常数级别的,O(1),不管有多少元素,一次计算出结果。但是这也有问题,因为不同的关键字带入同一个公式,可能计算出的结果是相同的,这就产生了冲突,所以这就是散列查找的另一个工作,解决冲突,如何计算位置,如何解决冲突,后面将会一一说明。

2 散列表(哈希表)

类型名称:符号表(SymbolTable)
数据对象集:符号表是“名字(Name)-属性(Attribute)”对的集合。

操作集:Table ∈ SymbolTable,Name ∈ NameType,Attr ∈ AttributeType
1、SymbolTable InitializeTable( int TableSize ):创建一个长度为TableSize的符号表;
2、Boolean IsIn( SymbolTable Table, NameType Name):查找特定的名字Name是否在符号表Table中;
3、AttributeType Find( SymbolTable Table, NameType Name):获取Table中指定名字Name对应的属性;
4、SymbolTable Modefy(SymbolTable Table, NameType Name, AttributeType Attr):将Table中指定名字Name的属性修改为Attr;
5、SymbolTable Insert(SymbolTable Table, NameType Name, AttributeType Attr):向Table中插入一个新名字Name及其属性Attr;
6、 SymbolTable Delete(SymbolTable Table, NameType Name):从Table中删除一个名字Name及其属性。

“散列(Hashing)” 的基本思想是:

  1. 以关键字key为自变量,通过一个确定的函数 h(散列函数),计算出对应的函数值h(key),作为数据对象的存储地址。
  2. 可能不同的关键字会映射到同一个散列地址上,可能不同的关键字会映射到同一个散列地址上,
    即h(keyi) = h(keyj)(当keyi ≠keyj),称为“冲突(Collision)”。
    ----需要某种冲突解决策略

下面用两个具体的例子来说明散列查找的具体过程,也好有一个直观的感受。
【例】 有n = 11个数据对象的集合{18,23,11,20,2,7,27,30,42,15,34}。
符号表的大小用TableSize = 17,选取散列函数h如下:
h(key) = key mod TableSize (求余)

地址 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
关键词 34 18 2 20 23 7 42 27 11 30 15
  • 存放:
    h(18)=1, h(23)=6, h(11)=11, h(20)=3, h(2)=2, …….
    如果新插入35, h(35)=1, 该位置已有对象!冲突!!
  • 查找:
    key = 22, h(22)= 5,该地址空,不在表中
    key = 30, h(30)= 13,该地址存放是30,找到!

装填因子(Loading Factor):设散列表空间大小为m,填入表中元素个数是n,则称α= n / m为散列表的装填因子
α=11 / 17 ≈ 0.65。

【例】将acos、define、float、exp、char、atan、ceil、floor、clock、ctime,顺次存入一张散列表中。
散列表设计为一个二维数组Table[26][2],2列分别代表2个槽。
设计散列函数h(key) = key[0] – ‘a’

将cos、define、float、exp、char、atan、ceil、floor、clockctime 依次装入散列表中,如下图所示,最后两个元素clock、ctime冲突,溢出了,没有地方放置。
散列表_数据结构心法指南
假设没有上面溢出的问题,看一下散列查找的时间复杂度:
T查询= T插入= T删除= O( 1 )

3 总结

  • 为什么要使用散列查找(动态查找,时间复杂度近似常数)
  • 散列查找基本工作:计算位置,解决冲突