目录
- 0.概述
- 0.1.分类
- 0.2.平均查找长度
- 1. 顺序查找、线性查找
- 2. 二分查找
- 3. 插值查找
- 4. 斐波那契查找
- 5. 树表查找
- 6. 分块查找
- 7. 哈希查找
- 参考
0.概述
0.1.分类
- 静态查找和动态查找:针对查找表而言的。动态表指查找表中有删除和插入操作的表。
- 无序查找和有序查找:
无序查找:被查找数列有序无序均可;
有序查找:被查找数列必须为有序数列。
0.2.平均查找长度
Average Search Length,ASL
需和指定key进行比较的关键字的个数的期望值,称为查找算法在查找成功时的平均查找长度。
1. 顺序查找、线性查找
说明: 顺序查找适合于存储结构为顺序存储或链接存储的线性表。
基本思想: 属于无序查找算法。
- 从线形表的一端开始,顺序扫描。
- 两两比较,若相等则表示查找成功。
- 若扫描结束,仍没有找到,表示查找失败。
时间复杂度:O(n)。
def sequential_search(lis, key):
length = len(lis)
for i in range(length):
if lis[i] == key:
return i
else: # 注意在for外面
return False
if __name__ == '__main__':
LIST = [1, 5, 8, 123, 22, 54, 7, 99, 300, 222]
result = sequential_search(LIST, 123)
2. 二分查找
说明: 元素必须是有序的,如果是无序的则要先进行排序操作。
基本思想: 属于有序查找算法。
- 从数组的中间元素开始,如果中间元素正好是要查找的元素,则查找过程结束。
- 如果要查找的元素大于/小于中间元素,则在数组大于/小于中间元素的那一半中查找。而且跟开始一样从中间元素开始比较。
- 如果在某一步骤数组为空,则代表找不到。
算法步骤:
Input:有序数组A[1…n],查找值T
Output:m
- 令
- 如果,则搜索失败
- 令中间元素索引
- 如果,令,返回步骤2
- 如果,令,返回步骤2
复杂度分析:
时间复杂度:O(logn),每次把搜索区域减少一半
空间复杂度:O(1)
def binary_search(nums, key):
l, r = 0, len(nums)-1
while l <= r:
m = (l + r) // 2
if nums[m] == key:
return m
elif nums[m] > key: # 在左半边
r = m - 1
else:
l = m + 1 # 在右半边
return False
if __name__ == '__main__':
nums = [1, 5, 7, 8, 22, 54, 99, 123, 200, 222, 444]
result = binary_search(nums, 99) # return 6
def binary_search(nums, key, l, r):
if l > r: return False
m = (l + r) // 2
if nums[m] == key:
return m
elif nums[m] > key: # 在左半边
return binary_search(nums, key, l, m-1)
else: # 在右半边
return binary_search(nums, key, m+1, r)
if __name__ == '__main__':
nums = [1, 5, 7, 8, 22, 54, 99, 123, 200, 222, 444]
result = binary_search(nums, 99, 0, len(nums)-1) # return 6
print(result)
3. 插值查找
与二分查找的区别:
因此,代码和二分查找仅有m赋值的区别。
def insert_search(nums, key, l, r):
if l > r: return False
m = l + int(((key - nums[l])/(nums[r] - nums[l]))*(r - l)) #二分查找为(l + r) // 2
if nums[m] == key:
return m
elif nums[m] > key: # 在左半边
return binary_search(nums, key, l, m-1)
else: # 在右半边
return binary_search(nums, key, m+1, r)
if __name__ == '__main__':
nums = [1, 5, 7, 8, 22, 54, 99, 123, 200, 222, 444]
result = insert_search(nums, 99, 0, len(nums)-1) # return 6
print(result)
emmm我写代码发现,两个严重的问题:
- 在nums很短、key很大时,很可能会报错!如下所示:
if __name__ == '__main__':
nums = [1, 5, 7]
result = insert_search(nums, 99, 0, len(nums)-1)
# m = 32, nums[32]
# IndexError: list index out of range
- 有博主说在【m=…】后,加入如下判断,可以避免1中问题。
m = l + int(((key - nums[l])/(nums[r] - nums[l]))*(r - l))
if(mid < low or mid > high): return False
然而,只是错觉,如下所示:
if __name__ == '__main__':
nums = [1, 5, 7,999,7]
result = insert_search(nums, 999, 0, len(nums)-1) # return False
# m = 665, l = 0, r = 4
# 进入if,return False
- m的计算容易出现“除数是0”的情况。
if __name__ == '__main__':
nums = [1, 5, 7,999,7,1]
result = insert_search(nums, 999, 0, len(nums)-1)
# ZeroDivisionError
综上:
对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比二分查找要好的多。
反之,数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。
尤其是,nums短,要查找的数很大时,不能使用!
复杂度分析:
时间复杂性:如果元素均匀分布,则O(log(logn)),在最坏的情况下可能需要 O(n)。
空间复杂度:O(1)。