04 查找与哈希算法
静态查找(Static Search):在查找过程中,该查找数据不会有添加、删除或更新等操作。
动态查找 (Dynamic Search)
-
顺序查找法/线性查找法
将数据一项一项地按顺序逐个查找,不管数据顺序如何,都得从头到尾遍历一次。
优点:查找前不需要任何处理与排序。
缺点:慢。适合小数据。 -
二分法查找
数据事先 排好序 了。
将数据分割成两等份,再比较键值与中间值的大小,如果键值小于中间值,可确定要查找的数据在前半段,否则在后半段。def bin_search(data, val): low = 0 high = len(data) - 1 while low <= high: mid = (low+high) // 2 if data[mid] < val: low = mid + 1 elif data[mid] > val: high = mid - 1 else: return mid return -1
-
插值查找法/插补查找法(Interpolation Search)
二分法的改进。
按照数据位置的分布,利用公式预测数据所在位置,在以二分法的方式逐渐逼近。
使用插值法是假设数据平均分布在数组中,而每一项的数据的差距相当接近或有一定的距离比例。
插值法的公式为:
Mid = low + (( key - data[low] ) / ( data[high] - data[low] )) * ( high - low )
其中,key 是要查找的键,data[high],data[low]是剩余待查数据中的最大、最小值。
假设数据有 n 项,插值法查找的步骤如下:
① 将记录按从小到大的顺序给予1、2、3、...、n的编号
② 令 low = 1,high = n
③ 当 low < high 时,重复 ④ 和 ⑤
④ 令 Mid = low + (( key - data[low] ) / ( data[high] - data[low] )) * ( high - low )
⑤ 若 key < keyMid 且 high ≠ Mid - 1,则令 high = Mid - 1;若 key = keyMid 成功查到;若 key > keyMid 且 low ≠ Mid + 1,则令 low = Mid + 1def interpolation_search(data, val): low = 0 high = len(data) - 1 while low < high: mid = low + int((val - data[low]) * (high - low) / (data[high] - data[low])) if mid > high: return -1 if data[mid] > val: high = mid - 1 elif data[mid] < val: low = mid + 1 else: return mid return -1
-
常见哈希法
- 除留余数法
将数据除以某一个常数后,取余数来当索引。
h( key ) = key mod B
B 最好是质数 - 平方取中法
例如 65,求平方得 4225,取百位数和十位数作为键值。f( 22 ) = 65。
如果有10个空间,而取百位和个位数的有100个(00~99)。需要把第一次求得的键除以 10 取整(压缩),才能将 100 个可能产生的值对应到 10 个空间。f( 2 ) = 65。 - 折叠法
将数据转换成一串数字后,先将这串数字拆成几个部分,再把它加起来,就可以计算出这个键值的 Bucket Address(桶地址)。
例如,转换后的数字位 1238765439873,以 4 个数字为一部分,1238、7654、3987、3,将四个数字相加即为索引值。这种直接相加的做法称为”移动折叠法“。
为了降低碰撞,可以将每一部分数字中的奇数和偶数反转再相加。这种做法叫”边界折叠法“(folding at the boundaries)。例如将偶数反转,8321、4567、3987、3 相加得出桶地址。 - 数字分析法
适用于数据不会更改,且为数字类型的静态表。
在决定哈希函数时先逐一检查数据的相对位置和分布情况,将重复性高的部分删除。
- 除留余数法
-
溢出( Overflow )与碰撞( Collision )
- 线性探测法
当发生碰撞时,若该索引对应的存储位置已有数据,则以线性的方式往后往后寻找空的存储位置,一旦找到就把数据放进去。线性探测法通常把哈希的位置视为环形结构,后面的位置被填满前面还有位置时,可以将数据放到前面。import random INDEXBOX = 10 # 哈希表可放元素个数 MAXNUM = 7 # 数据个数 def print_data(data, max_number): # 打印数组子程序 print('\t', end='') for i in range(max_number): print('[%2d] ' % data[i], end='') print() def create_table(num, index): # 建立哈希表子程序 tmp = num % INDEXBOX # 哈希函数 = 数据 % INDEXBOX while True: if index[tmp] == -1: # 如果数据对应的数据是空的 index[tmp] = num # 则直接存入数据 break else: tmp = (tmp + 1) % INDEXBOX # 否则往后找位置存放 # 主程序 index = [None] * INDEXBOX data = [None] * MAXNUM print('原始数组值:') for i in range(MAXNUM): data[i] = random.randint(1, 20) for i in range(INDEXBOX): index[i] = -1 print_data(data, MAXNUM) print('哈希表内容:') for i in range(MAXNUM): create_table(data[i], index) print(' %2d =>' %data[i], end='') print_data(index, INDEXBOX) print('完成哈希表:') print_data(index, INDEXBOX)
原始数组值:
[ 7] [13] [13] [12] [14] [15] [12]
哈希表内容:
7 => [-1] [-1] [-1] [-1] [-1] [-1] [-1] [ 7] [-1] [-1]
13 => [-1] [-1] [-1] [13] [-1] [-1] [-1] [ 7] [-1] [-1]
13 => [-1] [-1] [-1] [13] [13] [-1] [-1] [ 7] [-1] [-1]
12 => [-1] [-1] [12] [13] [13] [-1] [-1] [ 7] [-1] [-1]
14 => [-1] [-1] [12] [13] [13] [14] [-1] [ 7] [-1] [-1]
15 => [-1] [-1] [12] [13] [13] [14] [15] [ 7] [-1] [-1]
12 => [-1] [-1] [12] [13] [13] [14] [15] [ 7] [12] [-1]
完成哈希表:
[-1] [-1] [12] [13] [13] [14] [15] [ 7] [12] [-1] - 平方探测法
当溢出发生时,下一次查找的地址是 ( f(x)+i² ) mod B 与 ( f(x) - i² ) mod B。例如数据值 key,哈希函数 f:
第一次查找:f ( key )
第二次查找:( f ( key ) + 1² ) % B
第三次查找:( f ( key ) - 1² ) % B
第四次查找:( f ( key ) + 2² ) % B
第五次查找:( f ( key ) - 2² ) % B
... ...
第 n 次查找:( f ( key ) ± ( ( B-1 ) / 2 )² ) % B,其中,B必须为 4j + 3 型的质数,且 1 ≤ i ≤ ( B-1 ) / 2 - 再哈希法
先设置一系列哈希函数,如果使用第一种哈希函数出现溢出时就改用第二种,如果第二种也溢出,就是用第三种,...,一直到没有溢出。
- 线性探测法
用哈希法将下列 7 个数字存在0、1...6的 7 个位置:101,186,16,315,202,572,463。若要存入 1000 开始的 11 个 位置,如何存放?
data mod 7
( data mod 11 ) + 1000
碰撞:两个不同的数据经过哈希运算对应到相同的地址。