数据结构-散列
散列表: 散列查找: 查找的本质: 已知对象找位置。 有序安排对象:全序、半序 直接“算出”对象位置:散列 散列查找法的两项基本工作: 计算位置:构造散列函数确定关键词存储位置; 解决冲突:应用某种策略解决多个关键词位置相同的问题 时间复杂度几乎是常量:O ( 1 ), 即查找时间与问题规模无关! 散列表(哈希表) 类型名称:符号表(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.以存储元素为自变量,通过一个确定的函数 h(散列函数), 计算出对应的函数值h(key),作为数据对象的存储地址。 查找时,以同一个函数计算要查找的元素的函数值,根据函数值计算可能存储的地址,根据地址查找 2.冲突: 可能不同的关键字会映射到同一个散列地址上,即h(keyi) = h(keyj)(当keyi ≠keyj),称为“冲突(Collision)”。 散列函数的构造: 1.一个“好”的散列函数一般应考虑下列两个因素: 计算简单,以便提高转换速度; 关键词对应的地址空间分布均匀,以尽量减少冲突。 2.数字关键词的散列函数构造: 1.直接定址法 取关键词的某个线性函数值为散列地址,即 h(key) = a*key + b 2.除留余数法: 散列函数为:h(key) = key mod p //一般取素数 3.数字分析法 分析数字关键字在各位上的变化情况,取比较随机的位作为散列地址 如:取11位手机号码key的后4位作为地址: 3.字符关键词的散列函数构造 h(“abcde”)=‘a’*324+’b’*323+’c’*322+’d’*32+’e’ Index Hash ( const char *Key, int TableSize ) { unsigned int h = 0; /* 散列函数值,初始化为0 */ while ( *Key != ‘\0’) /* 位移映射 */ h = ( h << 5 ) + *Key++; return h % TableSize; } 处理冲突的方法: 1.换个位置: 开放地址法 1.一旦产生了冲突(该地址已有其它元素),就按某 种规则去寻找另一空地址 2.寻找另一空地址的方案:线性探测、平方探测、双散列: 线性探测法: 以增量序列 1,2,……,(TableSize -1)循环试探下一个存储地址。 当发生冲突时,寻找下一个位置,弱为空,则放在这个位置,否则继续往下找 可以证明,线性探测法的期望探测次数 满足下列公式: 对插入和不成功查找而言:p=0.5*(1+1/((1-a)^2)) //a为装填因子 对插入和不成功查找而言:p=0.5*(1+1/(1-a)) 平方探测法(Quadratic Probing)--- 二次探测 以增量序列1^2, -1^2, 2^2, -2^2......循环试探下一个存储地址。 当发生冲突时,向下移动1^2个位置,查看是否为空,为空则将数字放在这个位置,否则移动-1^2个位置,依次类推。。。 注意:平方探测法有可能出现由空位置,但探测不到的情况 有定理显示:如果散列表长度TableSize是某个4k+3(k是正整数)形式的素数时,平方探测法就可以探查到整个散列表空间。 双散列探测法: di 为i*h2(key),h2(key)是另一个散列函数 当发生冲突时,则移动h2(key)的位置,如果还不为空,则移动2*h2(key)的位置。。。 可以证明,平方探测法和双散列探测法探测次数 满足下列公式: 对插入和不成功查找而言:p=1/(1-a) //a为装填因子 对插入和不成功查找而言:p=-1/a * ln(1-a) 3.在开放地址散列表中,删除操作要很小心。通常只能“懒惰删除”,即需要增加一个“ 删除标记(Deleted)”,而并不是真正删除它。以便查找时不会“断链”。其空间可以在 下次插入时重用。 4.当散列表元素太多(即装填因子 α太大)时,查找效率会下降; //实用最大装填因子一般取 0.5 <= α<= 0.85 当装填因子过大时,解决的方法是加倍扩大散列表,这个过程叫做“再散列(Rehashing)”,扩大列表时,需要重新计算所有元素的位置,并将元素放在新的位置 2.同一位置的冲突对象组织在一起: 链地址法 分离链接法:将相应位置上冲突的所有关键词存储在同一个单链表中 所有地址链表的平均长度定义成装填因子α,α有可能超过1。 不难证明:其期望探测次数 p为: 对插入和不成功查找而言:p= a+e^(-a) 对成功查找而言: p = 1+a/2 3.两种解决冲突方法的比较: 开放地址法: 散列表是一个数组,存储效率高,随机查找。 散列表有“聚集”现象 分离链法: 散列表是顺序存储和链式存储的结合,链表部分的存储效率和查找效率都比较低。 关键字删除不需要“懒惰删除”法,从而没有存储“垃圾”。 太小的α可能导致空间浪费,大的α又将付出更多的时间代价。不均匀的链表长度导致时间效率的严重下降
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构