根據人們長時間接觸以來,發現計算機在計算某些一些簡單的數據的時候會表現的比較笨拙,而這些數據的計算會消耗大量計算機資源,並且耗時,這個時候就有人對這類計算編寫了一些策略,這些策略就是算法。這些策略會加快數據計算時間,大大減小計算機的資源消耗。算法
在長時間人們編寫代碼的工做中,一些優秀的算法就被流傳下來,可是不是全部的算法都能實現目的一步到位的工做,它只能減小你的代碼,提升工做效率,隨着知識的不斷積累,你會發現有更好的辦法來完成算法所完成的事情。ide
當有一個文件,文件裏面都是數字,要寫一段代碼查找用戶須要的數字在什麼位置。以前的知識咱們可能會使用循環去讀取這個文件的每一行並最終找到這個數字所在的位置,此類方法能夠達到目的,可是消耗了不少沒必要要的時間和資源。函數
這個其實也都想到了就是算法,上面的問題思路是這樣的,咱們首先把這個文件中數據的中間值取出來,讓用戶須要查找的數據這個這個中間值作比對,若是用戶輸入的數字大於這個中間值,那麼我就日後找,再取一次這個中間值,再作比較,這樣以此類推直到找到這個數字,並取出這個數字所在的位置便可。優化
#二分查找算法: #版本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新列表裏面,接下里進行優化。spa
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列表裏面怎麼辦。code
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:被查找到的數字不能被二次調用。
上面的二分查找算法是使用遞歸函數來完成的,通過上面的驗證,咱們隊遞歸函數有又新的理解。blog
1:只要寫遞歸函數,必定就須要結束的條件,而這個結束的條件就是你知道結果就應該要結束掉了;遞歸
2:返回值這個地方,須要看返回操做是在遞歸到第幾層的時候發生的,返回了給了誰,若是這個返回值不是返回到最外層函數,調用的層面是接收不到的,因此說這個返回值必定要返回到最外層函數;資源
3:在實際的場景中,只要能用算法解決的事情,一定多多少少會用到遞歸函數,並且因此語言都有遞歸的概念;event
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的問題,在以前的文章中有必定的介紹。