查找算法
-
影响查找时间长短的主要因素:
- 算法
- 数据存储的方式及结构
-
查找的分类:
-
静态查找
是指数据在查找过程中,该查找数据不会有添加、删除或更新等操作,例如符号表查找;
-
动态查找
是指所查找的数据,在查找过程中会经常性地添加、删除或更新,例如网络上查找数据;
-
-
判断好坏:由其比较次数及查找所需时间来判断
-
一般的查找技巧:通过各种不同的比较方法来查找所要的数据项
-
哈希法:直接通过数学函数来获取对应的存放地址
顺序查找
又称线性查找,方法是将数据一项一项地按顺序逐个查找,所以不管数据顺序如何,都要从头到尾遍历一次。
优点是文件在查找前不需要进行任何的处理与排序,缺点是查找速度较慢。
如果数据没有重复,找到数据就可终止查找的话,在最差的情况下是未找到数据,而需进行n次比较,最好情况下则是一次就找到数据,只需1次比较。
顺序查找是一种适用于小数据文件的查找方法。
import random
val = 0
data = [0] * 80
for i in range(80):
data[i] = random.randint(1, 150)
while val != -1:
find = 0
val = int(input('请输入查找键值(1-150),输入-1离开:'))
for i in range(80):
if data[i] == val:
print('在第 %3d个位置找到键值 [%3d]' % (i + 1, data[i]))
find += 1
if find == 0 and val != -1:
print('######没有找到 [%3d]######' % val)
print('数据内容为:')
for i in range(10):
for j in range(8):
print('%2d[%3d] ' % (i * 8 + j + 1, data[i * 8 + j]), end='')
print('')
二分查找
针对已经事先排好序的数据。
方法是将数据分割成两等份,再比较键值与中间值的大小,如果键值小于中间值,可确定要查找的数据在前半段,否则在后半段。利用分治思想,如此分割数据直到找到或确定不存在为止。
import random
def bin_search(data, val):
low = 0
high = 49
while low <= high and val != -1:
mid = int((low + high) / 2)
if val < data[mid]:
print('%d 介于位置 %d[%3d] 和中间值 %d[%3d] 之间,找左半边' \
% (val, low + 1, data[low], mid + 1, data[mid]))
high = mid - 1
elif val > data[mid]:
print('%d 介于中间值位置 %d[%3d] 和 %d[%3d] 之间,找右半边' \
% (val, mid + 1, data[mid], high + 1, data[high]))
low = mid + 1
else:
return mid
return -1
val = 1
data = [0] * 50
for i in range(50):
data[i] = val
val = val + random.randint(1, 5)
while True:
num = 0
val = int(input('请输入查找键值(1-150),输入-1结束:'))
if val == -1:
break
num = bin_search(data, val)
if num == -1:
print('##### 没有找到[%3d] #####' % val)
else:
print('在第 %2d个位置找到 [%3d]' % (num + 1, data[num]))
print('数据内容为:')
for i in range(5):
for j in range(10):
print('%3d-%-3d' % (i * 10 + j + 1, data[i * 10 + j]), end='')
print()
插值查找
又称插补查找法,是二分查找法的改进版。
按照数据位置的分布,利用公式预测数据所在的位置,再以二分法的方式渐渐逼近。
使用插值法是假设数据平均分布在数组中,而每一项数据的差距相当接近或有一定的距离比例。
import random
def interpolation_search(data, val):
low = 0
high = 49
print('查找过程中......')
while low <= high and val != -1:
mid = low + int((val - data[low]) * (high - low) / (data[high] - data[low])) # 插值查找法公式
if val == data[mid]:
return mid
elif val < data[mid]:
print('%d 介于位置 %d[%3d] 和中间值 %d[%3d] 之间,找左半边' \
% (val, low + 1, data[low], mid + 1, data[mid]))
high = mid - 1
elif val > data[mid]:
print('%d 介于中间值位置 %d[%3d] 和 %d[%3d] 之间,找右半边' \
% (val, mid + 1, data[mid], high + 1, data[high]))
low = mid + 1
return -1
val = 1
data = [0] * 50
for i in range(50):
data[i] = val
val = val + random.randint(1, 5)
while True:
num = 0
val = int(input('请输入查找键值(1-150),输入-1结束:'))
if val == -1:
break
num = interpolation_search(data, val)
if num == -1:
print('##### 没有找到[%3d] #####' % val)
else:
print('在第 %2d个位置找到 [%3d]' % (num + 1, data[num]))
print('数据内容为:')
for i in range(5):
for j in range(10):
print('%3d-%-3d' % (i * 10 + j + 1, data[i * 10 + j]), end='')
print()
哈希法
使用哈希函数来计算一个键值所对应的地址,进而建立哈希表格,然后依靠哈希函数来查找各个键值存放在表格中的地址。查找速度与数据量无关,在没有碰撞和溢出的情况下,一次读取即可完成。
常见的哈希算法:除留余数法、平方取中法、折叠法、数组分析法
除留余数法
将数据除以某个常数(建议选用质数)后,取余数来当索引。
平方取中法
先计算数据的平方,之后再取中间的某段数字作为索引
折叠法
将数据转换成一串数字后,先将这串数字拆成几个部分,再把它们加起来,就可以计算出这个键值的Bucket Address。
数组分析法
碰撞与溢出问题的处理
溢出:当标识符要放入某个桶(Bucket,哈希表中存储数据的位置)时,若该桶已经满了,就会发生溢出;
碰撞:键值不同,但经过哈希函数计算后的哈希值相同,则产生哈希碰撞问题。
常见的处理算法:线性探测法、平方探测法、再哈希法
-
线性探测法
线性探测法是当发生碰撞情况时,若该索引对应的存储位置已有数据,则以线性的方式往后寻找空的存储位置,一旦找到位置就把数据放进去。
线性探测法通常把哈希的位置视为环形结构,如此一来若后面的位置已被填满而前面还有位置时,可以将数据放到前面。
缺点:相类似的键值经常会聚集在一起
-
平方探测法
当溢出发生时,让数据值加或减i的平方。
-
再哈希法
一开始就先设置一系列的哈希函数,如果使用第一种哈希函数出现溢出时就改用第二种,如果第二种也出现溢出则改用第三种,一直到没有发生溢出为止。