PYTHON基础篇(五)
- 算法初识
- 什么是算法
- 二分查找算法
♣一:算法初识
A:什么是算法
根据人们长时间接触以来,发现计算机在计算某些一些简单的数据的时候会表现的比较笨拙,而这些数据的计算会消耗大量计算机资源,而且耗时,这个时候就有人对这类计算编写了一些策略,这些策略就是算法。这些策略会加快数据计算时间,大大减少计算机的资源消耗。
在长时间人们编写代码的工作中,一些优秀的算法就被流传下来,但是不是所有的算法都能实现目的一步到位的工作,它只能减少你的代码,提高工作效率,随着知识的不断积累,你会发现有更好的办法来完成算法所完成的事情。
B:二分查找算法
当有一个文件,文件里面都是数字,要写一段代码查找用户需要的数字在什么位置。之前的知识我们可能会使用循环去读取这个文件的每一行并最终找到这个数字所在的位置,此类方法可以达到目的,但是消耗了很多不必要的时间和资源。
这个其实也都想到了就是算法,上面的问题思路是这样的,我们首先把这个文件中数据的中间值取出来,让用户需要查找的数据这个这个中间值做比对,如果用户输入的数字大于这个中间值,那么我就往后找,再取一次这个中间值,再做比较,这样以此类推直到找到这个数字,并取出这个数字所在的位置即可。
#二分查找算法: #版本1: l = [2,4,5,6,8,13,22,24,44,56,66,67,68,88,89,90,99] def find(l,tag): #tag是用户输入要查找大数字 half_tag = len(l)//2 #取一半的下标,此处要使用整除,防止除不尽的情况 #假如中间值是44 if l[half_tag] < tag: #用取一半列表再去哥用户要找的数字比对 num_l = l[half_tag+1 :] #如果用户输入的数字大于一半的下标,那么就从44后面找,这个位置要加1,是因为44在大于的时候已经比对过了,要从56开始比对 #num_l = l[half_tag+1 :] #把上面切掉的一半数据赋予新的变量名。 find(num_l,tag) #从上面新变量里面再去找,用户输入的是不是大于或者小于列表,一直重复下去,直到找到数字取到下标 elif l[half_tag] > tag: num_l = l[:half_tag] #如果小于,就从24往前找,这个时候就不用加1,这个是切片的顾头不顾尾的原则 #half_tag = len(num_l)//2 find(num_l,tag) else: print('找到了!',half_tag,l[half_tag]) #最后一种情况就是找到了,那就直接打印即可。 find(l,66)
从上面的版本1,我们初步的把整个我们想实现目的的大致过程给写了出来,但是会发现有问题,我们要取的数字下标在l列表里面,结果出来的下标是1,这个和实际的情况不相符,我们要找的数字66是在l列表里面,不是在num_l新列表里面,接下里进行优化。
l = [2,4,5,6,8,13,22,24,44,56,66,67,68,88,89,90,99] def find(l,tag,start=0,end=len(l)): #既然上面我们发现错误点是在新列表里面找的,那么我们需要让代码不断的从原始的l列表里面找。 #那么就需要定义一个开始start和结束end,让下面的代码在l列表里面调整start和end的位置即可。 #half_tag=len(l)//2 如果还是使用len(l)//2的话,half_tag就会是一个新的列表,那么还是没有解决从l列表找的问题。 half_tag = (end - start)//2 + start #例如end表99的位置是20,start的值10,那么就是20-10/2=5,下标是5就不对了。 #下标是5就从l表前面去找了,所以要加一个start,这样取的就是l的中间值了。 if l[half_tag] < tag: find(l,tag,start=half_tag+1,end=end) #当取出一半的列表之后,find读取到用户在l列表里面找,开始的位置是上面比较之后的结果 #结束位置就是l列表结束的位置。 elif l[half_tag] > tag: find(l,tag,start=start,end=half_tag-1) else: print('找到了!',half_tag,tag) find(l,66)
版本2里面我们已经解决了每次从新列表取的bug了,但是还是存在问题,1:end这个参数有问题,2:返回值的问题,我们虽然取到了结果,但是这个结果不能用于其它代码去调用,功能单一了,3:要是用户找的数字不再l列表里面怎么办。
l = [2,4,5,6,8,13,22,24,44,56,66,67,68,88,89,90,99] #问题1:如果l列表在函数代码下面出现,find函数就会出现问题。 def find(l,tag,start=0,end=None): #要解决上面的问题,需要把end变成默认参数 end = len(l) if end is None else end #在通过三元运算符来返回end,当end是空的时候就返回len(l),否则返回end(用户传过来的指) half_tag = (end - start)//2 + start if start <= end: #在find函数内部不断在执行的时候,会不断给start,end传输新的指,那么如果函数里面出现开始的指大于或者等于结束指了。 #那么理论上就出现错误了,说明该数字就不存在表里面。 if l[half_tag] < tag: find(l,tag,start=half_tag+1,end=end) elif l[half_tag] > tag: find(l,tag,start=start,end=half_tag-1) else: print('找到了!',half_tag,tag) else: print('找不到该数字') #当开始大于等于结束指了,就直接报错找不到 find(l,23) #问题2:要是要找的数字不在l列表里面,find函数肯定会报错 # l = [2,4,5,6,8,13,22,24,44,56,66,67,68,88,89,90,99] # def find(l,tag,start=0,end=None): # end = len(l) if end is None else end # half_tag = (end - start)//2 + start # if start <= end: # if l[half_tag] < tag: # find(l,tag,start=half_tag+1,end=end) # elif l[half_tag] > tag: # find(l,tag,start=start,end=half_tag-1) # else: # return half_tag #如果我在此处使用return,他是直接把返回值给到了上一级的find,也就是判断l>tag地方的find,或者l<tag地方的find. # #因为函数内部在调用find,而这个find是不能接受这个返回值的,也就等于函数在上面的if地方就结束了。 # #函数在if的位置结束了,也就不会走到else的地方了,那么加在此处的return就return的是一个空,自然得到的结果就是none了 # else: # print('找不到该数字') # find(l,23) # #问题3:被查找到的数字不能被二次调用。 l = [2,4,5,6,8,13,22,24,44,56,66,67,68,88,89,90,99] def find(l,tag,start=0,end=None): end = len(l) if end is None else end half_tag = (end - start)//2 + start if start <= end: if l[half_tag] < tag: return find(l,tag,start=half_tag+1,end=end) elif l[half_tag] > tag: return find(l,tag,start=start,end=half_tag-1) else: return half_tag else: return '找不到该数字' #上面既然会出现大于,小于,等于,找不到四种情况,每种情况都有可能返回了指接收不了,那么我们 #每次调用的指都返回回去,这样最外层的find就不会中断继续执行了,整个find函数就可以继续执行了 ret=find(l,23) print(ret) #问题3:被查找到的数字不能被二次调用。
上面的二分查找算法是使用递归函数来完成的,经过上面的验证,我们队递归函数有又新的理解。
1:只要写递归函数,一定就需要结束的条件,而这个结束的条件就是你知道结果就应该要结束掉了;
2:返回值这个地方,需要看返回操作是在递归到第几层的时候发生的,返回了给了谁,如果这个返回值不是返回到最外层函数,调用的层面是接收不到的,所以说这个返回值一定要返回到最外层函数;
3:在实际的场景中,只要能用算法解决的事情,必定多多少少会用到递归函数,而且所以语言都有递归的概念;
4:递归函数,最好是从结果往前推。
def fib(x): if x==1 or x==2: return 1 return fib(x-1)+fib(x-2) fib1=fib(6) print(fib1)
上面的斐波拉契函数正常我们要查询数字比较小的,很快就能查出来,但是要是查询80或者100等,就会发现很慢,这个是因为return fib(x-1) + fib(x-2),这里面调用了两次fib函数,这会导致函数执行效率大打折扣,从表面我们看到fib(x-1)就是fib(5),fib(x-2)就是fib(4),但是程序在执行的时候,是要先算出fib(5)=fib(x-1)也就是fib(4),fib(4)=fib(x-1)也就是fib(3),就这样一层层的算到初始值1+1,如果是50,等于fib两边的数字先要分别以一种金字塔的形式分别算下去,这个就是导致程序执行慢的根本原因,所以说在递归函数,千万不要在内部调用多次。
def fib(x): if x==2: return 1,1 else: a,b=fib(x-1) #这个else里面的代码就是解决上面fib调用多次导致效率低的关键,因为你在计算3的时候你肯定知道是1+2,那么在fib计算一次之后 #我就计算的数据赋值给两个变量。 return b,a+b#上面得到的两个值为了不要重复计算我下一个指,我就把上一次计算的指返回去一个,例如:用户找4,那么就是fib(1)+fib(2), #用户在查询5的时候,我已经把fib(4)和fib(2)+fib(3)准备好了,且返回给了上一层的a,b两个变量,等于a,b重新被赋值了。 print(fib(3)) 解决查询的值会显示两位的问题 def fib(x,l=[0]): l[0]+=1 if x==1 or x==2: l[0]-=1 return 1,1 else: a,b=fib(x-1) l[0]-=1 if l[0]==0: return a+b return b,a+b print(fib(10))
def fac(x): if x==1: return 1 return x*fac(x-1) print(fac(5))
5:递归函数一定要考虑到最大递归的998的问题,在之前的文章中有一定的介绍。