查找算法
查找算法,又叫搜索算法,字面意思上解决查找问题的算法。
另外两种说法:
检索存储在某种数据结构中的信息的算法;
在问题域的搜索空间进行计算的算法
要素
输入数据:待查数集合、目标数
目的:确认待查数集合中目标数的存在性、存在位置
在我们的现实中应用某种算法的时候,数据是如何组织的往往已经是既定事实。在一般情况下,我们不是根据算法选择数据结构,而是面对现实的数据结构选择与这种数据结构相适应的具体算法。
顺序查找
顺序查找适用于存储结构为序列式的数据结构「线性数据结构」
基本原理
从待查数列的一端开始,依次遍历其中的每个元素,将元素数值与目标数相比较,若相等则查找成功;若遍历结束仍没有找到与目标数一致的元素,则查找失败。
![顺序查找](https://i-blog.csdnimg.cn/blog_migrate/1ac75775165aa2b3076a355a7a82d66e.jpeg)
# -*- coding: utf-8 -*-
# @Time : 2021/3/13 10:40 上午
# @Author : zhongxin
# @Email : 490336534@qq.com
# @File : 顺序查找.py
def find_num(arr, tn):
"""
:param arr: 待查数集合
:param tn: 目标数
:return:
"""
i = 0
while i < len(arr):
if arr[i] == tn:
print('查找成功')
break
else:
i += 1
else:
print('查找失败')
if __name__ == '__main__':
tn = 95
arr1 = [1, 5, 8, 19, 3, 2, 14, 6, 7, 22, 44, 95, 78]
arr2 = [1, 5, 8, 19, 3, 2, 14, 6, 7, 22, 44, 78]
find_num(arr1, tn) # 查找成功
find_num(arr2, tn) # 查找失败
衡量算法的性能
时间复杂度
算法需要消耗的时间资源
可以直观理解为算法中基本操作的个数「或叫作步数」
可以使用f(n)
表示,其中n
是问题规模
大O记号
选用一个特殊的符号来表达函数的量级,这个符号是O
大O是一个数学记号,描述了一个函数在其参数达到某一特定值或无穷大时的极限行为,这个记号体现了函数的增长率。
大O记号的作用是聚焦主要因素,忽略次要因素
几种时间复杂度
O(1)
常数时间O(log(n))
对数时间O(n)
线性时间O(nlog(n))
对数-线性时间O(n^2)
平方时间O(n^3)
立方时间O(2^n)
指数时间O(n!)
阶乘时间
# -*- coding: utf-8 -*-
# @Time : 2021/3/13 11:04 上午
# @Author : zhongxin
# @Email : 490336534@qq.com
# @File : 时间复杂度.png.py
import math
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0.1, 10, 99)
y0 = [1 for i in x]
y1 = x
y2 = x ** 2
y3 = x ** 3
y4 = [math.factorial(i) for i in range(10)]
y5 = [math.log2(i) for i in x]
plt.axis([0, 10, 0, 10])
plt.plot(x, y0, label='O(1)')
plt.plot(x, y1, label='O(n)')
plt.plot(x, y2, label='O(n^2)')
plt.plot(x, y3, label='O(n^3)')
plt.plot([i for i in range(10)], y4, label='O(n!)')
plt.plot(x, y5, label='O(log(n))')
plt.legend(loc='upper right')
plt.ylabel('y')
plt.xlabel('x')
plt.show()
![时间复杂度](https://i-blog.csdnimg.cn/blog_migrate/59e12c3bb28c42d2e2a362f2d169ddfb.jpeg)
顺序查找的时间复杂度
顺序查找的时间复杂度是
O(n)
假设待查数列
有n
个元素
任何一个元素与目标数
一致的可能性为1/(n+1)
n
个待查数分别与目标数相等及不相等共有(n+1)
种情况,那平均步数为:(1+2+3+...+n)/(n+1)=n/2
当我们只考虑运算的量级的时候可以说:「顺序查找的时间复杂度是O(n)
」
空间复杂度
算法需要消耗的存储空间资源
对于任何一个算法而言,只要它处理n个输入数据,就要把这些数据读入存储空间,所以对于任何问题规模为n的算法,它所需要消耗的存储空间至少是O(n)
除了程序体控制流程和输入数据占据空间,还有在算法过程中临时存储数据的缓存空间
二分查找
二分查找是一种在有序数列中查找某个特定元素的查找算法
![二分查找](https://i-blog.csdnimg.cn/blog_migrate/29c42a7889e3fc3478827b497945ab80.jpeg)
# -*- coding: utf-8 -*-
# @Time : 2021/3/13 11:59 上午
# @Author : zhongxin
# @Email : 490336534@qq.com
# @File : 二分查找.py
import random
def find_num2(arr, tn):
"""
:param arr: 待查数集合
:param tn: 目标数
:return: 索引
"""
low = 0 # 查找范围开始的索引
high = len(arr) - 1 # 查找范围结束的索引
while low <= high:
middle = int((high - low) / 2) + low # 防止超越整型数据上限
# middle = int((high + low) / 2)
if arr[middle] == tn:
print('查找成功!')
return middle
else:
if arr[middle] < tn:
low = middle + 1
else:
high = middle - 1
else:
print('查找失败!')
if __name__ == '__main__':
arr = list(range(0, 1000, random.randint(0, 10)))
tn = random.randint(0, 1000)
print(f'待查找的数据是:{arr}')
print(f'目标数字是:{tn}')
tn_index = find_num2(arr, tn)
if tn_index:
print(f'查找到{tn}的索引是{tn_index}')
时间复杂度
第一次循环n=n/1=n/2^0
第二次循环n/2=n/2^1
第三次循环n/4=n/2^4
第k次循环n/2^(k-1)
假设到了第k次循环,查找区间为1
n/2^(k-1) >= 1
n >= 2^(k-1)
log(n) >= log(2^(k-1))
log(n) >= k-1
k <= log(n)+1
所以循环的次数必定小于等于log(n)+1
所以二分查找的时间复杂度为O(log(n))
空间复杂度
没有使用任何额外的存储空间,所以空间复杂度为O(1)
重复数列二分查找
查找重复数字串的「头」或「尾」
# -*- coding: utf-8 -*-
# @Time : 2021/3/13 2:08 下午
# @Author : zhongxin
# @Email : 490336534@qq.com
# @File : 重复数二分查找.py
def fund_num2_1(arr, tn, delta):
"""
:param arr: 待查数集合
:param tn: 目标数
:param delta:
* -1 : 往前挪
* 1 : 往后挪
:return: 索引
"""
low = 0 # 查找范围开始的索引
high = len(arr) - 1 # 查找范围结束的索引
while low <= high:
middle = int((high - low) / 2) + low
if arr[middle] == tn:
print('查找成功!')
while 0 <= middle + delta < len(arr) and arr[middle + delta] == tn:
middle += delta
return middle
else:
if arr[middle] < tn:
low = middle + 1
else:
high = middle - 1
else:
print('查找失败!')
if __name__ == '__main__':
arr = [3, 3, 3, 5, 5, 5, 5, 9, 7, 12, 15, 15, 18, 32, 66, 78, 94, 103, 269, 269]
tn = 269
tn_index = fund_num2_1(arr, tn, 1)
print(tn_index)
找到目标数字后使用while继续逐个查找,这时候的时间复杂度是O(n)
while 0 <= middle + delta < len(arr) and arr[middle + delta] == tn:
middle += delta
如果在这部分继续使用二分查找,那么就可以将时间复杂度降低为O(log(n))
if 0 <= middle + delta < len(arr) and arr[middle + delta] == tn:
if delta < 0:
high = middle - 1
else:
low = middle + 1
else:
return middle
不包含重复元素旋转数列的二分查找
# -*- coding: utf-8 -*-
# @Time : 2021/3/13 2:42 下午
# @Author : zhongxin
# @Email : 490336534@qq.com
# @File : 不包含重复元素旋转数列的二分查找.py
def binary_search_in_rotated_sequence(arr, tn):
low = 0
high = len(arr) - 1
while low <= high:
m = int((high - low) / 2) + low
if arr[m] == tn:
return m
else:
if arr[m] < tn:
if arr[m] < arr[low] <= tn:
high = m - 1
else:
low = m + 1
else:
if arr[m] > arr[high] >= tn:
low = m + 1
else:
high = m - 1
return -1
if __name__ == '__main__':
arr = [i for i in range(11, 21)] + [i for i in range(1, 11)]
tnList = [1, 2, 7, 10, 11, 15, 20]
for tn in tnList:
r = binary_search_in_rotated_sequence(arr, tn)
print(f'tn:{tn}在{arr}的第{r}个')
旋转点:旋转数组中最小的元素
情况A,旋转点在中位数的右侧:
中位数以及它左侧的元素,全部是升序的。
最左侧元素,必定小于等于中位数。
查找目标出现在左侧的条件是:最左侧元素 <= 查找目标 < 中位数
情况B,旋转点在中位数的左侧:
中位数以及它右侧的元素,全部是升序的。
最左侧元素,必定大于中位数。
查找目标出现在右侧的条件是:中位数 < 查找目标 <= 最右侧元素
![不包含重复元素旋转数列的二分查找](https://i-blog.csdnimg.cn/blog_migrate/5e16fa81141624250b405f69dc0885c2.jpeg)
# -*- coding: utf-8 -*-
# @Time : 2021/3/13 2:42 下午
# @Author : zhongxin
# @Email : 490336534@qq.com
# @File : 不包含重复元素旋转数列的二分查找.py
def binary_search_in_rotated_sequence(arr, tn):
low = 0
high = len(arr) - 1
while low <= high:
m = int((high - low) / 2) + low
if arr[m] == tn:
return m
else:
if arr[m] >= arr[low]:
if arr[m] > tn >= arr[low]:
high = m - 1
else:
low = m + 1
else:
if arr[m] < tn <= arr[high]:
low = m + 1
else:
high = m - 1
return -1
if __name__ == '__main__':
arr = [i for i in range(11, 21)] + [i for i in range(1, 11)]
tnList = [1, 2, 7, 10, 11, 15, 20]
for tn in tnList:
r = binary_search_in_rotated_sequence(arr, tn)
print(f'tn:{tn}在{arr}的第{r}个')