1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
| ''' 冲突的处理方法 一、换个位置:开放地址法 二、同一位置的冲突对象组织在一起:链地址法
开放地址法 Open Addressing: 一旦冲突,按照某种规则取寻找另一空地址。 若发生第i次冲突,试探的下一个地址将增加di, hi(key) = (h(key) + di) mod TableSize i 属于 [1,TableSize] di 决定来不同的解决冲突方案: 线性探测:di = i 平方探测:di = + - i^2 双散列: di = i*h2(key) # 引入另一个散列函数 经验证明 以下形式比较好: h2(key) = p - (key mod p) p为质数 ''' ''' 线性探测法: 关键词序列{47,7,29,11,9,84,54,20,30} 散列表表长度tablesize13,装填因子9/13, 散列函数 h(key) = key mod 11 # 注意取11是小于13的,之后的线性探测是%13取余! 用线性探测法处理冲突,列出一次插入后的散列表,估算查找性能。 答: h(47) = 3 , h(7) = 7, h(29) = 7 遇到冲突, 试一试 d1 = 1,h1(29) = (7+1) mod 11 = 8 可以存放。 h(11) = 0, h(9) = 9, h(84) = 7 遇到冲突, 试一试 d1 = 1, h1(84) = 8,8位置已经有存放了,还是冲突, 试一试 d2 = 2,h2(84) = 9, 9位置已经有存放了,还是冲突, 试一试 d3 = 3, h3(84) = 10,可以存放。 dn 可以很大,在散列表中循环到前面的位置。 ... 从老师的图中可以看出,当散列出现集中冲突的时候, 存放会在一个区域形成聚集。
散列查找性能分析 一、成功"平均"查找长度ASLs 二、不成功"平均"查找长度ASLu ---------------------------------------------------- H 0,1,2,3,4,5,6,7,8,9,10,11,12 key 11,30, 47, 7,29,9, 84, 54, 20 冲突次数 0,6, 0 0,1,0,3, 1, 3 ----------------------------------------------------
如何确定元素在散列表中? 冲突0次,找一次就找到了,冲突1次,要找2次才能找到。 成功平均查找次数 = (1+7+1+1+2+1+4+2+4)/9 = 23/9 = 2.56 如何确定元素不在散列表中? 以 h = 0 为例,如果h =0处没有该元素也不能断定散列表中一定没有该元素, 因为可能由于线性探测的位移移动到了前面或者后面的位置, 一直要循环移动,直到遇到空位置,才能断定该元素一定不在散列表中。 h = 0 不在散列表中,h =1 时该元素也不在散列表中,h = 2 发现是空位, 所以,要3次 才能断定 h = 0 的元素确实不在散列表中。 不成功平均查找长度 = (3+2+1+2+1+1+1+9+8+7+6)/11 = 41/11 = 3.73 注意: h(key) = key mod 11 计划就是存放11个数,之不过散列表有13个空间。 不成功的查找只会考虑0~10的位置。就是按计划找那几个位置。
例子 将acos\define\float\exp\char\atan\ceil\floor 8个元素顺次存入一张大小为26的散列表中。 h(key) = key[0] - 'a' 采用线性探测 di = i, --------------------------------------------------------- 0, 1, 2, 3, 4, 5, 6, 7, 8, ... acos char define exp float --------------------------------------------------------- atan 遇到第一个冲突 --------------------------------------------------------- 0, 1, 2, 3, 4, 5, 6, 7, 8, ... acos atan char define exp float --------------------------------------------------------- 最终 --------------------------------------------------------- 0, 1, 2, 3, 4, 5, 6, 7, 8, ... acos atan char define exp float ceil floor --------------------------------------------------------- 成功平均查找比较次数 ASLs = (1*5 + 2 + 5 + 3)/8 = 15/8 = 1.87 不成功平均查找次数 h(key)计划分为26种情况计划存放26个数。 ASLu = (9+8+7+6+5+4+3+2+ 1*18)/26 = 62/26 = 2.38 ''' ''' 平方探测法: 以增量序列 1^2、-1^2、2^2 -2^2 ... q^2 -q^2。 注意:第1次冲突 1^2, 第2次冲突 -1^2,第3次冲突 2^2 ... 还是以上上个案例看: ---------------------------------------------------- H 0,1, 2,3,4,5,6,7,8,9,10,11,12 key 11,30,20,47, 84,7,29,9, 54 冲突次数 3 3 2 1 ---------------------------------------------------- ASLs = (1*5 + 2 + 3 + 4 + 4)/9 = 2
线性探测 时,只要元素在散列表中,一个一个位移找总能找着正确的位置, 而 平方探测(二次探测)可能一直跳来跳去,找不到真正的位置。 跳着找不到位置的案例: h(key) = key mod 5 ----------------- H 0,1,2,3,4 key 5 6 7 ----------------- 一直循环。
二次探测(平方探测)也有聚集现象,但是没有线性探测的聚集严重。 有结论: 散列表 TableSize 是 4k+3 形式的质数时,平方探测法可以查到整个散列空间。
'''
class HashTable(): class Data(): def __init__(self,element=None,inf=None): self.element = element self.inf = inf def __init__(self,tablesize,thecells=[]): MinTableSize = 10 if tablesize < MinTableSize: print('散列表太小,直接用数组即可。') return None self.tablesize = tablesize self.TableSize = self.NextProme(tablesize) self.TheCells = [self.Data(None,None) for _ in range(self.TableSize)] def NextProme(self,tablesize): return 13 def Hash(self,key): return key%self.tablesize def Insert(self,item): Pos = self.Find(item) if self.TheCells[Pos].inf == None: self.TheCells[Pos].inf = '占用' self.TheCells[Pos].element = item def Find(self,key): cNum = 0 CurrentPos = self.Hash(key) NewPos = CurrentPos while self.TheCells[NewPos].inf != None and \ self.TheCells[NewPos].element != key: if cNum%2: NewPos = CurrentPos + (cNum//2 + 1)**2 while NewPos >= self.TableSize: NewPos -= self.TableSize else: NewPos = CurrentPos - (cNum//2)**2 while NewPos < 0: NewPos += self.TableSize cNum += 1 return NewPos def Delete(self,positon): self.TheCells[positon].inf = None print('已经删除了散列表%d位置的元素'%positon)
if __name__ == '__main__': h = HashTable(10) h.Insert(5) h.Insert(4) h.Insert(14) h.Insert(26) h.Insert(34) h.Insert(9) h.Insert(19) h.Insert(29) h.Insert(39) h.Delete(10) for i in h.TheCells: print(i.inf,i.element) print('用49 填补 已经被删除的原本19所在的位置。\n') h.Insert(49) for i in h.TheCells: print(i.inf,i.element) ''' 再散列 Rehashing 当散列表中元素太多时,装填因子a太大,查找效率会下降, 理想a 属于(0.5~0.85) 解决办法是rehash,把散列表变大。
比如 原来11个空的hash表,装了9个元素后,a值太大了,我们rehash扩大散列表, 用一个23个空的hash表来存数据,不能把之前的元素原位置copy过来, 而应该9个元素全部重算一遍,根据新的位置放入hash表。
'''
|