数据结构-散列

散列表:
散列查找:
    查找的本质: 已知对象找位置。  
        有序安排对象:全序、半序  
        直接“算出”对象位置:散列 
    散列查找法的两项基本工作:
        计算位置:构造散列函数确定关键词存储位置; 
        解决冲突:应用某种策略解决多个关键词位置相同的问题 
        
    时间复杂度几乎是常量: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.两种解决冲突方法的比较:
        开放地址法:
            散列表是一个数组,存储效率高,随机查找。
            散列表有“聚集”现象
        分离链法:
            散列表是顺序存储和链式存储的结合,链表部分的存储效率和查找效率都比较低。
            关键字删除不需要“懒惰删除”法,从而没有存储“垃圾”。
            太小的α可能导致空间浪费,大的α又将付出更多的时间代价。不均匀的链表长度导致时间效率的严重下降

 

posted @ 2019-11-24 14:19  foreast  阅读(376)  评论(0编辑  收藏  举报