19:04:41 2019-09-17
已知的查找方式
顺序查找 $O(N)$
二分查找(静态查找) $O(LogN)$
二叉搜索树 $O(h)$ h为二叉查找树的高度
平衡二叉树 $O(LogN)
查找的本质:已知对象找位置
有序安排对象:全序(二分查找) 半序(二叉搜索树)
直接“算出”对象位置:散列
散列查找法的两项基本工作:
计算位置:构造散列函数 确定关键词存储位置
解决冲突:引用某种策略 解决多个关键词位置相同的问题
时间复杂度几乎是常量:$O(1)$,及查找时间与问题规模无关
散列表(哈希表)
类型名称:符号表(SymbolTable)
数据对象集:符号表是"名字(Name)-属性(Attribute)"的集合
如果没有溢出 $T_查询=T_插入=T_删除=O(1)$
”散列(Hashing)"的基本思想是:
(1)以关键字Key为自变量,通过一个确定的函数h(散列函数) 计算出对应的函数值h(Key),作为数据对象的存储地址
(2)可能不同的关键字会映射到同一个散列地址上,
即$h(key_1)=h(key_2) 当(key_1 ot=key_2)$,称为“冲突(Collision)”---------需要某种冲突解决策略
散列函数的构造方法
一个好的散列函数一般应考虑下列两个因素:
1.计算简单,以便提高转换速度
2.关键词对应的 地址空间分布均匀,以尽量减少冲突
数字关键词的散列函数构造
1.直接定址法
取关键词的某个线性函数值为散列地址,即$h(key)=a*key+b (a、b为常数)$
2.除留余数法
散列函数为 $h(key)=key mod p$
这里 $p=TableSize$
一般,$p$取素数
3.数字分析法
4.折叠法
把关键词分割成位数相同的几个部分,然后叠加
5.平方取中法
字符关键词的散列函数构造
1.一个简单的散列函数-----ASCII码加和法
对字符型关键词key定义散列函数如下:
$h(key)=(sum key[i]) mod TableSize$
2.简单的改进------前3个字符移位法
$h(key)=(key[0]*27^2+key[1]*27+key[2])mod TableSize$
3.好的散列函数------移位法
涉及关键词所有n个字符,并且分布得很好:
$h(key)=Big (sum_{i=0}^{n-1} key[n-i-1]*32^i Big) mod TableSize
冲突处理方法
常用的思路
换个位置:开放地址法
同一位置的冲突对象组织在一起:链地址法
开放地址法(Open Addressing)
一旦产生了冲突(该地址已有其它元素),就按某种规则取寻找另一空地址
若发生了第i次冲突,试探的下一个地址将增加$d_i$,基本公式是
$h_i(key)=(h(key)+d_i)mod TableSize (1leq i les TableSize)$
$d_i$决定了不同的解决冲突方案:线性探测、平方探测、双散列
线性探测:$d_i=i$
平方探测:$d_i=pm i^2$
双散列:$d_i=i*h_2(key)$
1.线性探测法(Linear Probing)
以增量序列$1,2,3,4,.......,(TableSize-1)$循环试探下一个存储地址 (线性探测很容易聚集)
2.平方探测法(Quadratic Probing) ----二次探测
平方探测法:以增量序列$1^2,-1^2,2^2,-2^2,......,q^2,-q^2$且$qleq TableSize/2$循环试探下一个存储地址
有定理显示:如果散列表长度TabLeSize是某个$4k+3$(k是正整数)形式的素数时,平方探测法就可以探查到整个散列表空间
在开发地址散列表中,删除操作要很小心,通常只能懒惰删除,即需要增加一个"删除标记(Deleted)“,而并不是真正删除它,以便查找时不会"断链",其空间可以在下次插入时重用
3.双散列探测法(Double Hashing)
双散列探测法:$d_i为i*h_2(key),h_2(key)是另一个散列函数$
探测序列成$h_2(key),2h_2(key),3h_2(key),.......$
$对任意的key,h_2(key) ot=0$
探测序列还应该保证所有的散列存储单元都应该能被探测到
选择以下形式
$h_2(key)=p-(key mod p)$
其中 $p<TableSize,p、TableSize 都是素数$
4.再散列(Rehashing)
当散列表元素太多(即装填因子 $alpha A$太大)时,查找效率会下降
实用最大装填因子一般取$ 0.5leq alpha A leq0.85$
散列表扩大时,原有元素需要重新计算放置到新表中
分离链接法(Separate Chainig)
将相应位置上冲突的所有关键词存储在同一个单链表中
开放地址法的实现

1 //创建开放定址法的散列表实现 2 #include<stdio.h> 3 #include<math.h> 4 #include<malloc.h> 5 #define MAXTABLESIZE 100000 //允许开辟的最大散列表长度 6 typedef int ElementType; //关键词类型使用整型 7 typedef int Index; //散列地址类型 8 typedef Index Position; //数据所在位置与散列地址是同一类型 9 /*散列单元状态类型,分别对象:有合法元素、空单元、有已删除元素*/ 10 typedef enum{Legitimate,Empty,Deleted}EntryType; 11 typedef struct HashEntry Cell; //散列表单元类型 12 struct HashEntry 13 { 14 ElementType Data; //存放元素 15 EntryType Info; //单元状态 16 }; 17 18 typedef struct TblNode* HashTable; //散列表类型 19 struct TblNode //散列表节点定义 20 { 21 int TableSize; //表的最大长度 22 Cell* Cells; //存放散列单元数据的数组 23 }; 24 25 int NextPrime(int N) 26 {//返回大于N且不超过MAXTABLESIZE的最小素数 27 int i, p = (N % 2) ? N + 2 : N + 1; //从大于N的下一个奇数开始 28 while (p<=MAXTABLESIZE) 29 { 30 for (i = (int)sqrt(p); i > 2; i--) 31 if (p % i == 0)break; //p不是素数 32 if (i == 2)break; //p是素数 结束循环 33 else 34 p += 2; //试探下一个素数 35 } 36 return p; 37 } 38 39 HashTable CreateTable(int TableSize) 40 { 41 HashTable H; 42 int i; 43 H = (HashTable)malloc(sizeof(struct TblNode)); 44 //保证散列表最大长度是素数 45 H->TableSize = NextPrime(TableSize); 46 //声明单元数组 47 H->Cells = (Cell*)malloc(sizeof(ElementType) * H->TableSize); 48 //初始化单元状态为"空单元" 49 for (i = 0; i < H->TableSize; i++) 50 H->Cells[i].Info = Empty; 51 return H; 52 } 53 54 int Hash(ElementType Key, int TableSize) 55 { 56 return Key % TableSize; 57 } 58 //平方探测法的查找与插入 59 Position Find(HashTable H, ElementType Key) 60 { 61 Position CurrentPos, NewPos; 62 int CNum = 0; 63 NewPos = CurrentPos = Hash(Key, H->TableSize); 64 while (H->Cells[NewPos].Info!=Empty&&H->Cells[NewPos].Data!=Key) 65 { 66 //统计冲突次数 并且判断奇偶次 67 if (++CNum % 2) 68 {//奇数次 69 NewPos = CurrentPos + (CNum / 2) * (CNum / 2); 70 while (NewPos >= H->TableSize) 71 NewPos -= H->TableSize; //调整为合法地址 72 } 73 else 74 {//偶数次 75 NewPos = CurrentPos - (CNum / 2) * (CNum / 2); 76 while (NewPos < 0) 77 NewPos += H->TableSize; //调整为合法地址 78 } 79 } 80 return NewPos; 81 } 82 83 void Insert(HashTable H, ElementType Key) 84 { 85 Position Pos = Find(H, Key); 86 if (H->Cells[Pos].Info != Legitimate) 87 { 88 H->Cells[Pos].Data = Key; 89 H->Cells[Pos].Info = Legitimate; 90 } 91 }
链地址法的实现

1 #include<stdio.h> 2 #include<malloc.h> 3 #include<math.h> 4 #include<string.h> 5 #define KEYLENGTH 15 //关键词字符串最大长度 6 #define MAXTABLESIZE 10000 7 8 typedef char ElementType[KEYLENGTH + 1]; //关键词类型用字符串 9 typedef int Index; //散列地址类型 10 11 typedef struct LNode* PtrToLNode; 12 struct LNode 13 { 14 ElementType Data; 15 PtrToLNode Next; 16 }; 17 typedef PtrToLNode Position; 18 typedef PtrToLNode List; 19 20 typedef struct TblNode* HashTable; //散列表类型 21 struct TblNode //散列表节点定义 22 { 23 int TableSize; //表的最大长度 24 List Heads; //指向链表头节点的数组 25 }; 26 27 int NextPrime(int N) 28 { 29 int i; 30 int p = (N % 2)?N + 2:N + 1; 31 while (p<MAXTABLESIZE) 32 { 33 for (i = (int)sqrt(p); i > 2; i--) 34 if (p % i)break; 35 if (i == 2)break; 36 else 37 p += 2; 38 } 39 return p; 40 } 41 int Hash(ElementType Key, int TableSize) 42 { 43 44 } 45 HashTable CreateTable(int TableSize) 46 { 47 HashTable H; 48 int i; 49 50 H = (HashTable)malloc(sizeof(struct TblNode)); 51 //保证散列表的最大长度是素数 52 H->TableSize = NextPrime(TableSize); 53 54 //分配链表头节点数组 55 H->Heads = (List)malloc(H->TableSize * (struct LNode)); 56 //初始化表头节点 57 for (i = 0; i < H->TableSize; i++) 58 { 59 H->Heads[i].Data[0] = '