一、搜索算法
搜索算法又叫查找算法。在日常生活中,几乎每天都要进行一些查找的工作,在电话簿中查阅某个人的电话,在电脑的文件夹中查找某个具体的文件等等。
查找表是由同一类型的数据元素构成的集合。例如电话号码簿和字典都可以看作是一张查找表。
一般对于查找表有以下几种操作:
在查找表中查找某个具体的数据元素;
在查找表中插入数据元素;
从查找表中删除数据元素;
静态查找表和动态查找表:
在查找表中只做查找操作,而不改动表中数据元素,称此类查找表为静态查找表;
在查找表中做查找操作的同时进行插入数据或者删除数据的操作,称此类表为动态查找表。
关键字又细分为主关键字和次关键字。
若某个关键字可以唯一地识别一个数据元素时,称这个关键字为主关键字,例如学生的学号就具有唯一性;
像学生姓名、年龄这类的关键字,由于不具有唯一性,称为次关键字。
当然,我们的搜索算法比较常用的就是顺序查找和折半查找,下面我们一一介绍。
顺序查找算法
从表中的最后一个数据元素开始,逐个同记录的关键字做比较, 如果匹配成功,则查找成功;
如果直到表中第一个关键字查找完也没有成功匹配,则查找失败。
具体实现和结果如下:
def sequence_search(array,key):
"""
顺序查找
"""
for i in range(len(array)):
if array[i]==key:
return i
return False
array=[1,5,7,4,6,3]
print(sequence_search(array,4))
这个结果就会返回4这个元素在表中的索引下标。
折半查找算法
折半查找又叫二分法。
具体实现和结果如下:
def halffind(nums,key,low,high):
"""
:param nums: 查找的对象
:param key: 查找的元素
"""
mid = (low+high)//2
if key == nums[mid]:
return mid
if low >high:
return False
if key>nums[mid]:
return halffind(nums,key,mid +1,high)
else:
return halffind(nums, key, low, mid - 1)
nums=[1,2,3,4,5,6,7,8]
print(halffind(nums,3,0,(len(nums)-1)))
这个算法我们主要是用递归来实现的,每次需要查找的元素和中间值mid进行比较。最终找到元素位置,返回3这个元素在表中的索引下标。
二、贪心算法
在对问题求解时,总是作出在当前看来是最好的选择。也就是说,不从整体上加以考虑,它所作出的仅仅是在某种意义上的局部最优解(是否是全局最优,需要证明)。
我们平时其实也有不觉中使用了贪心算法,只是我们没有发觉。下面我们通过两个问题来具体理解贪心算法。
最优装载问题
有一天海盗们截获了一艘装满各种各样古董的货船,每一件都价值连城,一旦打碎就是去了价值,海盗船载重量为C,每件固定的重量为wi,海盗们该如何尽可能装载最多数量的古董呢?
- 船载重量固定为C,只要每次选择重量最小的古董,直到不能再装为止,这样装载的古董数量最大,
这就是贪心策略; - 把古董按重量从小到大排序,根据策略选出尽可能多的古董。
具体实现和结果如下:
def max_ans(antique,key):
anti_sort =sorted(antique)
ans,tmp=0,0
ship=[]
for a in anti_sort:
tmp +=a
if tmp <=key:
ans +=1
ship.append(a)
print("装载的古董数量:",ans)
print("装载的古董:",ship)
antique=[4,3,15,5,12,6,7,8]
max_ans(antique,40)
背包问题
假设山洞中有n种宝物,每种宝物有一定重量w和相应的价值v,毛驴运载能力一种宝物只能拿一样,宝物可分割。怎样才能使毛驴运走宝物的价值最大呢?
可以尝试三种贪心策略:
3. 每次挑选价值最大的装东西入背包;
4. 每次挑选最重的东西;
5. 每次选取单位重量价值最大的东西。
我们现在来看下我们的算法设计:
- 计算出每件宝物的性价比,按照从高到低排序;
- 根据贪心策略,按性价比从大到小选取宝物,直到达到毛驴的运载能力。每次选择宝物后判断是否
小于m,如果不小于则取走宝物的一部分,程序结束
具体实现和结果如下:
# datas中每个元素代表一个古董,每个列表第一个元素代表古董重量,第二个元素代表古董价值
datas = [[4, 3], [2, 8], [9, 18], [5, 6], [5, 8], [8, 20], [5, 5], [4, 6], [5, 7], [5, 15]]
m = 30 # 毛驴运载能力
w = 0 # 获取的总价值
# 计算出每件宝物的性价比,按照从高到低排序
for i in range(len(datas)):
price = datas[i][1] / datas[i][0]
datas[i].append(price) # 增加性价比
datas.sort(key=lambda data: data[2], reverse=True) # 按性价比排序
# 按性价比从大到小选取宝物,直到达到毛驴的运载能力
for data in datas:
if data[0] <= m:
w += data[1]
m -= data[0]
else:
w += data[2] * m # 取走宝物的一部分
break
print('总价值:',w)
这个问题的难点就在于分割宝物,理解起来有些困难。
我们试想一下如果宝物不可分割,贪心算法得到的是否是最优解?
注意: 物品可分割的装载问题称为背包问题,不可分割问题的装载问题称为0-1背包问题。
0-1背包问题不具有贪心选择性质,贪心算法不能得到全局最优解,仅仅是最优解的近似解。
0-1背包问题可用动态规划算法求解。
动态规划
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。
通过两个实例来具体了解动态规划。
动态规划之Fib数列
问题: 有个小孩上楼梯,共有N阶楼梯,小孩一次可以上1阶,2阶。走到N阶楼梯,一共有多少种走法?
DP之自顶向下分析方式:
爬到第N阶楼梯,一共只有2种情况(全划分,加法原理),从第N-1阶爬1阶到第N阶;从第N-2阶爬2阶到第N阶;
故:way(N)=way(N-1)+way(N-2)
具体实现和结果如下:
def fib2(n):
memo=[-1 for x in range(n+1)]
memo[0]=0
memo[1]=1
memo[2]=2
for i in range(3,n+1):
memo[i]=memo[i-1]+memo[i-2]
return memo[n]
print(fib2(5))
运行结果就是3+5=8种走法。
不相邻数最大和
问题: 给定数组A=[1,2,4,1,7,8,3],求出数组A中互不相邻的数的最大和。
例如:如果选择了8,则不能选择7和3,在本例中最大的和为1+4+7+3=15
具体实现和结果如下:
arr=[1,2,4,1,7,8,3]
def dp_opt(arr):
len_arr=len(arr)
opt = [0 for i in range(len_arr)]
opt[0]=arr[0]
opt[1]=max(arr[0],arr[1])
for i in range(0,len(arr)):
A=opt[i-1]
B=arr[i]+opt[i-2]
opt[i]=max(A,B)
return opt
print(dp_opt(arr))
返回的是我们选择每一个数字的最大和,很明显最后一个的时候最大。也就是我们的预期结果1+4+7+3=15。