创作不易,来了的客官点点关注,收藏,订阅一键三连❤
程序=数据结构+算法,算法是数学理论和工程实现的杂糅,是一个十分有趣神奇的学问。搞懂算法用另一种视角看编程,又会是一种全新的感受,如果你也在学习算法,不妨跟主任萌新超差一起学习,拿下算法!
python每日算法——算法的起步与递归算法(汉诺塔问题)
总结
本期的内容将介绍四大查找算法,即顺序查找、二分查找、插值查找和斐波那契查找,通过本期内容你不仅能知道他们的代码如何用python实现,还将学会用装饰器来查看算法的运行时间等等!
查找
什么是查找
四大查找方法
列表查找(线性表查找)
顺序查找
代码示例
二分查找
代码示例
利用装饰器查看顺序查找与二分查找的效率
顺序查找和二分查找哪个快?
代码示例
思考:python的列表查找用的是什么方法?
插值查找
举例说明
代码示例
二分查找与插值查找
斐波那契查找
斐波那契数列(Fibonacci)
斐波那契查找
代码示例
查找:在一些数据元素中,通过一定的方法找出与给定关键字相同的数据元素的过程。
顺序(线性)查找
二分查找
插值查找
斐波那契查找
列表查找:从列表中查找指定元素,分为顺序查找与二分查找
输入:列表、待查找元素
输出:元素的下标(未找到元素时一般返回None或者-1)
python中列表查找函数:index()
顺序查找:也称为线性查找,从列表第一个元素开始,顺序进行搜索,直到找到目标元素或搜索到最后一个元素为止。(最糟糕的形式:从头找到尾)
def linear_search(lst,value):
for ind,v in enumerate(lst):
if v == value:
return ind
else:
return None
lst1 = [2,3,23,13,25]
print(linear_search(lst1,13))
# 结果:3
顺序查找时间复杂度:O(n)
二分查找:二分查找输入的是一个有序的元素列表(必须有序)。如果查找的元素包含在列表中,二分查找返回其位置;否则返回null。
一般而言,对于包含n个元素的列表,用二分查找最多需要log2N步。
举例说明:
例1:
从以下列表查找元素3:
[1,2,3,4,5,6,7,8,9]
查找步骤:
利用二分查找,先设置1为left,9为right,则中间的值mid(5)为(left+right)//2,此时5>3,说明目标值在right的左边;
此时将right移到4,left不变,则mid(left+right)//2=2,2<3,说明在mid值的右边;
随后将left移到了3,right(4)位置不变,mid=(left+right)//2=3,输出mid的下标,若最后找不到,则输出null。
def func1(lst,val):
left = 0
right = len(lst) - 1
while left <= right: # 候选区有值
mid = (left + right)//2 # python中用地板除
if lst[mid] == val:
return mid
elif lst[mid] > val: # 待查找值在mid的左边
right = mid - 1
elif lst[mid] < val: # 待查找值在mid的右边
left = mid + 1
else:
return None
my_list = [1,13,6,3,7,14]
print(func1(my_list,6))
# 结果:2
二分查找时间复杂度:O(logn)
例2:
假设你要在字典里查找一个单词,而该字典包含240000个单词你认为每种查找最多需要多少步?
对于简单查找,最多将需要240000步;而对于二分查找,每次可以排除一半的单词,直到最后只剩下一个单词,最多需要17步!
import time
# 以下代码是求运行时间的装饰器
def runtime(func):
def inner(*args,**kwargs):
start = time.time()
a = func(*args,**kwargs)
end = time.time()
print(f"{func.__name__}执行用时{end-start}s")
return a
return inner
# 顺序查找代码示例
@runtime
def linear_search(lst,value):
for ind,v in enumerate(lst):
if v == value:
return ind
else:
return None
# 二分查找代码示例
@runtime
def binary_search(lst,val):
left = 0
right = len(lst) - 1 # left和right记录的都是下标
while left <= right: # 候选区有值
mid = (left + right)//2 # python中用地板除
if lst[mid] == val:
return mid
elif lst[mid] > val: # 待查找值在mid的左边
right = mid - 1
elif lst[mid] < val: # 待查找值在mid的右边
left = mid + 1
else:
return None
lst1 = list(range(10000))
print(linear_search(lst1,8888))
print(binary_search(lst1,8888))
# 输出结果
# linear_search执行用时0.001005411148071289s
# 8888
# binary_search执行用时0.0s(太小了,忽略不计)
# 8888
python中的列表查找一定是顺序查找,虽然二分查找效率高,散是有一个前提是需要列表是有序的,因此python中的列表查找是顺序查找。
当然如果列表是一个有序列表,我们可以选择二分查找。
插入排序(Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
图片来自菜鸟教程
以查字典为例,在我们从《新华字典》查找“赞”的时候,我们会下意识的翻开靠后面的书页。
同样的,以取值范围在 1~10000 间的 100 个元素从小到大均匀分布的数组中查找 5,那么自然会考虑从数组下标较小的开始查找。此时,二分查找这种查找方式,并不是自适应的,因此,基于二分查找,我们有了插值查找,目的是将查找点的选择改进为自适应选择,从而提高查找效率。
简单来说,插值查找是二分查找的优化,就是根据要查找的关键字 key 与查找表中最大最小记录的关键字比较后的查找方法,其核心在于插值的计算公式。
def interpolation_search(lst, key):
low = 0
high = len(lst) - 1
while (lst[low] <= key <= lst[high] and lst[low] != lst[high]):
mid = low + int((key - lst[low]) * (high - low) / (lst[high] - lst[low]))
if lst[mid] < key:
low = mid + 1
elif lst[mid] > key:
high = mid - 1
else:
return mid
if key == lst[low]:
return low
else:
return None
lst1 = [1,2,13,5,9,8]
print(interpolation_search(lst1,8))
# 结果:5
时间复杂度来说,其最坏时间复杂度也是 O ( logn )
对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找,速度较快
关键字分布不均匀的情况下,该方法不一定比二分查找要好
斐波那契数列:又称黄金分割数列,指的是这样一个数列:1,1,2,3,5,8,13,21,…
在数学上,斐波那契被递归方法如下定义:F(1) = 1;F(2) = 1;F(N) = F(n-1)+F(n-2) (n>=2),该数列越往后,相邻的两个数的比值越趋于黄金比例值(0.618)。斐波那契查找就是在二分法查找的基础上根据斐波那契数列进行分割。
斐波那契查找也是二分法查找的优化,也需要提前对数列记性排序。
当被查找数列长度为20时,二分法首次查找选择的是数列中第10个元素做参考物,Fibonacci首次查找按照选取的是数列中第13个元素做参考物;
如果被查找数列长度为100,二分法首次查找选择的是数列第50个元素做参照物,而斐波那契数首次查找选取的数列中第89个元素做参考物;
数列越长,选择参考点的位置差别越大。具体需要根据斐波那契数列的递归方法选取得到。
def fibonacci_search(lst, key):
# 需要一个现成的斐波那契数列表,其中最大元素的值必须超过查找表中元素个数的数值
F = [1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946]
low = 0
high = len(lst) - 1
# 为了使得查找表满足斐波那契特性,在表的最后添加几个同样的值,这个值是原查找表的最后那个元素的值
# 添加的个数由F[k] - 1 - high决定
k = 0
while high > F[k] - 1:
k += 1
print(k)
i = high
while F[k] - 1 > i:
lst.append(lst[high])
i += 1
print(lst)
# 算法主逻辑。time用来展示循环的次数
time = 0
while low <= high:
time += 1
# 为了防止F列表下标溢出,设置if和else
if k < 2:
mid = low
else:
mid = low + F[k - 1] - 1
print(f"low={low}, mid={mid}, high={high}")
if key < lst[mid]:
high = mid - 1
k -= 1
elif key > lst[mid]:
low = mid + 1
k -= 2
else:
if mid <= high:
# 打印查找的次数
print(f"times: {time}")
return mid
else:
print(f"times:{time}")
return high
print("times:{time}")
return None
lst1 = [1,5,8,13,1788,128,168,188,888]
print(fibonacci_search(lst1,888))
# 结果 :
# 6
# [1, 5, 8, 13, 1788, 128, 168, 188, 888, 888, 888, 888, 888]
# low=0, mid=7, high=8
# low=8, mid=10, high=8
# times:2
# 8 (最终的输出)
斐波那契查找的时间复杂度:O(logn )
创作不易,客官点个赞,评论一下吧!一起加油❤