Lesson_06
【4】集合(set)
在我的世界里你就是唯一,集合是個專一的類型
那么什么是集合了,集合就是沒有key的dict,我們看個例子:
dict1 = {1,2,3,4,5,4,3,2,1}
print(dict1,type(dict1))
#{1, 2, 3, 4, 5} <class 'set'>
上面提到了,set是很專一的,它會把重復(fù)的類型給去除掉。
那么set不像dict那樣是映射關(guān)系,那么我們怎么去找它的索引了。
dict1 = {1,2,3,4,5,4,3,2,1}
print(dict1[1])
#TypeError: 'set' object does not support indexing
上面的例子中出現(xiàn)了錯誤,為什么了,因為set是無序的,也就是它的索引會變動,所以要取出set中的值要把它轉(zhuǎn)成其它類型,才能訪問索引。
創(chuàng)建set
set1 = {'nihao',2,3,'hello'}
set2 = set()
set3 = set([1,2,3,4,5])
print(set1,type(set1))
print(set2,type(set2))
print(set3,type(set3))
#{'nihao', 2, 3, 'hello'} <class 'set'>
#set() <class 'set'>
#{1, 2, 3, 4, 5} <class 'set'>
課堂小練習(xí):
去除list中的重復(fù)值
list1 = [1,2,3,4,5,4,3,2,1]
print(list(set(list1)))
#[1, 2, 3, 4, 5]
訪問集合
由于集合是無序的,所以了不能用下標(biāo)的方法進(jìn)行訪問,我們可以用迭代的方法:
set1 = [1,2,3,4,5,6,7]
for v in set1:
print(v,end=' ')
#1 2 3 4 5 6 7
當(dāng)然也可以使用 in 和 not in 判斷值在不在set中:
set1 = [1,2,3,4,5,6,7]
print(2 not in set1)
print(1 in set1)
#False
#True
也可以使用add()和remove()去添加和刪除元素
set1 = {1,2,3,4,5,6,7}
set1.add(8)
set1.remove(1)
print(set1)
{2, 3, 4, 5, 6, 7, 8}
【5】不可變集合
有的時候我們也希望集合更具有穩(wěn)定性,也就是像元祖那樣不能隨意的增加或刪除集合中的元素。那么我們定義不可變的集合,這里使用的是frozenset()函數(shù),沒錯,就是把元素frozen(冰凍)。
set1 = frozenset({1,2,3,4,5,6,7})
set1.add(8)
print(set1)
#AttributeError: 'frozenset' object has no attribute 'add'
s8 = set([1,2,3])
s9 = set([2,3,4])
#交集
a1 = s8 & s9
print(a1)
print(type(a1))
#并集
a2 = s8 | s9
print(a2)
print(type(a2))
#差集
a3 = s9 ^ s8
print(a3)
print(type(type(a3)))
#{2, 3}
#<class 'set'>
#{1, 2, 3, 4}
#<class 'set'>
#{1, 4}
#<class 'type'>
【5】再回首,類型轉(zhuǎn)換
以前我們學(xué)習(xí)了int、float、str、bool等類型的轉(zhuǎn)換,
現(xiàn)在我們學(xué)習(xí)set、dict、tuple、list的類型轉(zhuǎn)換。
#list-->set
l1 = [1,2,3,4,5,3,4,5]
s1 = set(l1)
print(s1)
#tuple-->set
t2 = (1,2,3,4,3,2)
s2 = set(t2)
print(s2)
#set-->list
s3 = {1,2,3,4}
l3 = list(s3)
print(l3)
#set-->tuple
s4 = {2,3,4,5}
t4 = tuple(s4)
print(t4)
來一個有趣的練習(xí):
使用zip(key,value)函數(shù)
使用zip函數(shù)的是把兩個list或者tuple合拼成一個((key,value),key,value))格式
val = (1,2,3,4,5)
key = ['one','two','three','foul','five']
dict1 = dict(zip(key,val))
print(dict1)
#{'one': 1, 'two': 2, 'three': 3, 'foul': 4, 'five': 5}
二維矩陣變換(矩陣的行列互換)
比如我們有一個由列表描述的二維矩陣
zip([seql, ...])接受一系列可迭代對象作為參數(shù),將對象中對應(yīng)的元素打包成一個個tuple(元組),然后返回由這些tuples組成的list(列表)。若傳入?yún)?shù)的長度不等,則返回list的長度和參數(shù)中長度最短的對象相同。
a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(list(zip(*a)))
#[(1, 4, 7), (2, 5, 8), (3, 6, 9)]
另一種生成方式,生成器生成列表
print(list(x for x in range(10)))
print(tuple(x for x in range(10)))
print(set(x for x in range(10)))
#[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
#(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
#{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
【6】迭代器
可迭代對象
可迭代對象:可以直接作用于for循環(huán)的對象統(tǒng)稱為可迭代對象(Iterable)??梢杂胕sinstance()去判斷一個對象是否是Iterable對象
可以直接作用于for的數(shù)據(jù)類型一般分兩種
1、集合數(shù)據(jù)類型,如list、tuple、dict、set、string
2、是generator,包括生成器和帶yield的generator function
from collections import Iterable
#首先引入collections文件Iterable的類
from collections import Iterable
print(isinstance([], Iterable))
print(isinstance((), Iterable))
print(isinstance({}, Iterable))
print(isinstance("", Iterable))
print(isinstance((x for x in range(10)), Iterable))
print(isinstance(1, Iterable))
#True
#True
#True
#True
#True
#False
迭代器
迭代器:不但可以作用于for循環(huán),還可以被next()函數(shù)不斷調(diào)用并返回下一個值,直到最后跑出一個StopIteration錯誤表示無法繼續(xù)返回下一個值
可以被next()函數(shù)調(diào)用并不斷返回下一個值的對象稱為迭代器(Iterator對象)
可以使用isinstance()函數(shù)判斷一個對象是否是Iterator對象
from collections import Iterator
from collections import Iterator
print(isinstance([], Iterator))
print(isinstance((), Iterator))
print(isinstance({}, Iterator))
print(isinstance("", Iterator))
print(isinstance((x for x in range(10)), Iterator))
#False
#False
#False
#False
#True
可迭代對象指針下移
l = (x for x in [23,4,5,64,3435])
print(next(l))
print(next(l))
print(next(l))
print(next(l))
print(next(l))
# print(next(l))
#23
#4
#5
#64
#3435
##如果后面已經(jīng)沒有值了,指針繼續(xù)下移會報錯
轉(zhuǎn)成Iterator對象
from collections import Iterator
a = iter([1,2,3,4,5])
print(next(a))
print(next(a))
print(isinstance(iter([]), Iterator))
print(isinstance(iter(()), Iterator))
print(isinstance(iter({}), Iterator))
print(isinstance(iter(''), Iterator))
#1
#2
#True
#True
#True
#True
第十章
【1】函數(shù)
認(rèn)識函數(shù):在一個完整的項目中,某些功能會反復(fù)的使用。那么會將功能封裝成函數(shù),當(dāng)我們要使用功能的時候直接調(diào)用函數(shù)即可
本質(zhì):函數(shù)就是對功能的封裝
優(yōu)點
1、簡化代碼結(jié)構(gòu),增加了代碼的復(fù)用度(重復(fù)使用的程度)
2、如果想修改某些功能或者調(diào)試某個BUG,還需要修改對應(yīng)的函數(shù)即可
一、python的‘’積木“
函數(shù)就像是積木一樣通過創(chuàng)意和想想可以自由的組合,從而使我們的代碼,變的更加的簡單易懂,節(jié)約開發(fā)時間,不需要去理解底層的運行,把復(fù)雜的事情變的簡單有樂趣。
在學(xué)習(xí)這一章節(jié)之前了,其實我們就已經(jīng)接觸過BIF了,例如print()、range()這些都是python自己封裝的函數(shù)提供給我們使用的
1、創(chuàng)建和調(diào)用函數(shù)
? 函數(shù)的定義:
'''
關(guān)鍵字 def 函數(shù)名():
函數(shù)體
return 返回表達(dá)式
調(diào)用 函數(shù)名()
'''
#函數(shù)名可以以使用大小寫字母、下劃線開頭,不能以數(shù)字開頭
def mydef():#關(guān)鍵字def 函數(shù)名():
print('這是我的第一個函數(shù)')
print('我表示非常的激動...')
print('在這里感謝我的父母,感謝千鋒,你們成就了我')
mydef()
#注意:在函數(shù)名后面一定要加上小括號,小括號的作用是用來保存參數(shù)的
#函數(shù)的運行機制:mydef()發(fā)生調(diào)用操作時,python會自動往上去尋找 def mydef():定義的過程,然后依次執(zhí)行該代碼塊內(nèi)所包含的所用代碼部分。
調(diào)用代碼三次:
def mydef():
print('這是我的第一個函數(shù)')
print('我表示非常的激動...')
print('在這里感謝我的父母,感謝千鋒,你們成就了我')
for i in range(3): #使用循環(huán)反復(fù)的調(diào)用
mydef()
'''
result:
這是我的第一個函數(shù)
我表示非常的激動...
在這里感謝我的父母,感謝千鋒,你們成就了我
這是我的第一個函數(shù)
我表示非常的激動...
在這里感謝我的父母,感謝千鋒,你們成就了我
這是我的第一個函數(shù)
我表示非常的激動...
在這里感謝我的父母,感謝千鋒,你們成就了我
'''
2、函數(shù)的參數(shù)
函數(shù)的參數(shù)就是放在括號里的東西,參數(shù)可以實現(xiàn)鳥槍換大炮的功能,總之就是是函數(shù)可以個性化
def mydef(name): #從某種意義上來說括號中的參數(shù)就是個變量
print(name + '是帥鍋')
mydef('劉德華')
mydef('郭富城')
mydef('張學(xué)友')
mydef(
3、函數(shù)的返回值
有些時候,我們需要函數(shù)給我返回一些數(shù)據(jù)來報告執(zhí)行的結(jié)果,而不是想上面的代碼塊那樣直接去輸出打印結(jié)果。其實非常的簡單,只需要在函數(shù)中使關(guān)鍵字return,后面跟上的就是制定要返回的值。
def add(num1,num2):
return num1 + num2
print(add(1,2))
#注意:當(dāng)函數(shù)內(nèi)部使用的是return的時候,調(diào)用的時候沒有print()的話,是不會打印輸出的
#如果函數(shù)沒有了參數(shù),那么函數(shù)就變的非常的單一,一個函數(shù)就只能完成一個功能
二、靈活強大的用法
有時候我們評價一種編程語言是否優(yōu)秀,往往我們看的是它夠不夠靈活。靈活并非意味它無所不能。猶如我們上述的參數(shù)一樣,函數(shù)沒有了參數(shù)就會變的非常的死板,只能完成單一的功能
1、形參和實參
參數(shù)從調(diào)用的角度來說,分為形式參數(shù)和實際參數(shù)。python和絕大數(shù)語言一樣,形參指的是函數(shù)創(chuàng)建和定義過程中的小括號里面的參數(shù)(可以理解衛(wèi)一個新的變量)而實參指的是函數(shù)在調(diào)用的時候傳遞進(jìn)來的參數(shù)或值
def mydef(name): #小括號里面的name就是形參
return name
myname = '成龍'
print(mydef(myname)) #調(diào)用時候的myname就是實參
#在這個代碼段中把 myname 中的值(成龍)賦值給了 name
#相當(dāng)于 name = myname
#可以理解為結(jié)婚以后夫妻的財產(chǎn)是共有的,沒有為什么,法律規(guī)定的,這里也一樣,語法規(guī)定的
#不論是形參還是實參都只是一個概念和名詞,只需要去記住他,不需要去糾結(jié)為什么
2、函數(shù)的文檔
給函數(shù)寫文檔是為了讓別人更好的去理解你的函數(shù)的,所以這是一個好習(xí)慣。特別是在大型的項目開發(fā)中,就需要閱讀別人的文檔,因此適當(dāng)?shù)奈臋n非常的重要。
def mydef(params):
#引號中的部分就是我們書寫文檔的地方 并且文檔部分應(yīng)該寫在函數(shù)的開頭
'''
這是我們第一個文檔
:param params:
:return:
'''
return params
#print(mydef.__doc__) #顯示文檔的內(nèi)容 函數(shù)名(不帶小括號).__doc__
help(mydef)
#當(dāng)函數(shù)不知道怎么去使用的時候可以用help()去查看文檔,當(dāng)然使用方法需要自己去寫
3、關(guān)鍵字參數(shù)
普通的參數(shù)叫位置參數(shù),通常在調(diào)用的時候,粗心的程序員很容易弄錯參數(shù)的順序,導(dǎo)致函數(shù)達(dá)不到預(yù)期的效果。我們看一下例子:
def mydef(name,age):
print(name,'他的年齡是',age,'歲')
mydef(21,'hal')
#應(yīng)為參數(shù)順序錯誤導(dǎo)致結(jié)果不正確
#result:21 他的年齡是 hal 歲
因此,有了關(guān)鍵字參數(shù)。使用關(guān)鍵子參數(shù)可以有效的避免這種情況的發(fā)生。
def mydef(name,age):
print(name,'他的年紀(jì)是',age,'歲')
mydef(age=23,name='小米')
#使用了關(guān)鍵字參數(shù)以后順序錯了,但是結(jié)果依然正確,所謂的關(guān)鍵字就是指定的形參
#result:小米 他的年紀(jì)是 23 歲
4、默認(rèn)參數(shù)
初學(xué)者很容易把關(guān)鍵字參數(shù)和默認(rèn)參數(shù)搞混,默認(rèn)參數(shù)是在設(shè)定形參的時候給形參賦的值
def mydef(name='千鋒',action='是最好的IT教育機構(gòu)'):
print(name,action)
mydef()
#在參數(shù)沒有傳遞值的時候也不會報錯,因為有了默認(rèn)的值
#result:千鋒 是最好的IT教育機構(gòu)
mydef(name='中國',action='是最偉大的國家')
#在參數(shù)傳遞的過程中,替換了默認(rèn)的值
#result:中國 是最偉大的國家
通過上面的例子可以看出來關(guān)鍵字參數(shù)是在調(diào)用函數(shù)的時候使用的,而默認(rèn)參數(shù)是在定義函數(shù)的時候使用的
5、收集參數(shù)(可變參數(shù))
這個名字聽起來比較的新鮮。發(fā)明這種機制的人就是不知道在特定的情況下會用到多少的參數(shù)...聽起來您認(rèn)不解,但是確實有這種情況,這時候需要在參數(shù)前面加上(*)星號就可以了
def test(* params):
print('params 有%d個參數(shù)'%len(params))
print('最后一個參數(shù)是:',params[-1])
print(type(params))
print(params)
test(1,3,5,7,9)
'''
result:
params 有5個參數(shù)
最后一個參數(shù)是: 9
<class 'tuple'>
(1, 3, 5, 7, 9)
'''
#調(diào)用的時候傳遞多個參數(shù),python會把多個收集參數(shù)打包成tuple(元祖)
#所以在查找第幾個參數(shù)的時候可以使用元祖的查找方法
星號(*)既可以是打包,又可以是解包,舉個例子,如果我現(xiàn)在實參是一個list是那么便會變成嵌套,此時在調(diào)用的實參前面加沙個一個星號(*)進(jìn)行解包就就可以了
def test(* params):
print('有%d個參數(shù)' % len(params))
print(params[-1])
print(type(params))
print(type(params[-1])) #list是被嵌套在tuple里面的
print(params)
list = [1,3,5,7,9]
test( list)
'''
result:
有1個參數(shù)
[1, 3, 5, 7, 9]
<class 'tuple'>
<class 'list'>
([1, 3, 5, 7, 9],)
'''
為了是用起來更加的方便我們試一試解包
def test(* params):
print('有%d個參數(shù)' % len(params))
print(params[-1])
print(type(params))
print(type(params[-1]))
print(params)
list = [1,3,5,7,9]
test(* list) #解包以后的list是逐個傳遞的
'''
result:
有5個參數(shù)
9
<class 'tuple'>
<class 'int'>
(1, 3, 5, 7, 9)
'''
三、函數(shù)的地盤
1、函數(shù)的過程
很多編程語言中,函數(shù)和過程其實是區(qū)分開的。一般認(rèn)為函數(shù)(function)是返回值的,而過程(procedure)是簡單、特殊并且沒有返回值的。
也就是說,函數(shù)是干完事兒必須寫報告的“苦逼”,而過程是完事后拍拍屁股一走了之的“小混蛋”。
python嚴(yán)格來說只有函數(shù),沒有過程!但是通過上面的例子看過去,沒有return之前,python的function也是沒有返回值?我們看看下面的例子
def mydef():
print(123)
print(mydef())
#123
#None
#沒有return默認(rèn)返回一個None
2、談?wù)劮祷刂?/h5>
python可以利用序列打包多種類型的一次性返回值。
def mydef():
return 1,2,3,4
print(mydef())
#(1, 2, 3, 4)
3、函數(shù)變量的作用域
變量的作用域也就是平時所說的變量可見性
def discounts(price,rate):
final_price= price * rate
return final_price
old_price = 80
rate = 0.5
new_price = discounts(old_price,rate)
print(new_price)
print(final_price)
#40.0
#NameError: name 'final_price' is not defined
錯誤的原因是final_price沒有被定義過,因為他是discounts函數(shù)里面的內(nèi)容,再有在函數(shù)體內(nèi)部才有效,出了這個范圍就不是它的地盤了。
總結(jié)一下:在函數(shù)里面定義的參數(shù)以及變量,都稱謂局部變量,出了函數(shù)外,它們都是無效的。事實上,python在運行函數(shù)的時候,是利用內(nèi)存中的‘棧(stack)’區(qū)域進(jìn)行儲存的,當(dāng)執(zhí)行完函數(shù)以后,函數(shù)中的所有內(nèi)容都要被自動刪去,所以在函數(shù)體外是找不到的。
和局部變量相對應(yīng)的就是全局變量,全局變量擁有更加龐大的作用域
def discounts(price,rate):
final_price= price * rate
print(old_price)
return final_price
old_price = 80
rate = 0.5
new_price = discounts(old_price,rate)
print(new_price)
#80 #old_price在函數(shù)內(nèi)部輸出
#40.0
看起來全局變量更加霸道,但是我們看看下面的例子:
def discounts(price,rate):
old_price = 100
final_price= price * rate
print('這是里面的old_price',old_price)
return final_price
old_price = 80
rate = 0.5
new_price = discounts(old_price,rate)
print('這是外面的old_price',old_price)
print(new_price)
#這是里面的old_price 100
#這是外面的old_price 80
#40.0
這樣的話就已經(jīng)出現(xiàn)奇怪的問題。
其實函數(shù)里面的old_price是python自己創(chuàng)建的新的局部變量,但是全局的old_price紋絲不動。
四、內(nèi)嵌函數(shù)和閉包
1、global關(guān)鍵字
剛剛我們在上一個例子中看到了Python中對于全局變量的保護(hù),也就是在函數(shù)內(nèi)部重新復(fù)制的話,python會創(chuàng)建一個變量名一樣的局部變量,但是在不經(jīng)意中會出現(xiàn)衡多的bug,代碼的維護(hù)也會變得困難。
衡多編程語言為了避免這一個情況,都會使用global關(guān)鍵字,也就是說我們上面的那種方法就不要在用了。例子如下:
count = 5
def myfun():
global count
count = 10
return count
print(myfun())
print(count)
#10
#10
看上面的例子內(nèi)部的count重新復(fù)制,外層的count也隨之改變
2、內(nèi)嵌函數(shù)
什么是內(nèi)嵌函數(shù),就是允許在函數(shù)內(nèi)部創(chuàng)建另外一個函數(shù)。
def myfun():
print('fun1')
def myfun2():
return 'fun2'
return myfun2()
print(myfun())
#fun1
#fun2
這個函數(shù)雖小,但五臟俱全,不過看起來好像沒什么用。
<font color='red'>注意:myfun2是myfun1的內(nèi)部函數(shù),所以它的作用域也在myfun1的函數(shù)體內(nèi)。如果要在外部調(diào)用就會報錯。</font>
3、閉包(closure)
閉包是函數(shù)重要的組成結(jié)構(gòu),函數(shù)編程是一種編程范式。著名的函數(shù)編程語言LISP,這個大家可能知道,主要用于繪圖和人工智能,一直被認(rèn)為是天才使用的語言。
那么語言不同,那么閉包的的實現(xiàn)方式也就不同。
python中的閉包定義:一個內(nèi)部函數(shù)里,對外部的作用域(而不是全局作用域)的變量進(jìn)行引用,那么內(nèi)部函數(shù)就被認(rèn)為是閉包。看例子:
def funx(x):
def funy(y):
return x * y
return funy #這里是不帶括號的
#兩種調(diào)用格式
i = funx(8)
print(i(5))
print(funx(10)(5))
#40
#50
<font color='red'>注意:它和閉包是一個意思,所以不能直接去調(diào)用funy(),否則會報錯。</font>
閉包中的變量關(guān)系對應(yīng)
在閉包中外面一層函數(shù)中定義的變量是不能再內(nèi)層函數(shù)中直接修改的,只能訪問,相當(dāng)于全局變量和局部變量的關(guān)系,看例子:
def funx():
x = 5
def funy():
x *= x
return x
return funy #這里是不帶括號的
print(funx()())
#UnboundLocalError: local variable 'x' referenced before assignment
這個報錯信息和全局變量是一樣的,python認(rèn)為內(nèi)部函數(shù)的x是局部變量,外部的函數(shù)x被屏蔽了起來。所以
五、lambda表達(dá)式(匿名函數(shù))
python允許使用 lambda 關(guān)鍵字來創(chuàng)建匿名函數(shù),我們提到一個新的關(guān)鍵字匿名函數(shù)。那是一個什么函數(shù)了?匿名函數(shù)和普通的函數(shù)在使用上又有什么不同了,使用匿名函數(shù)又有哪些優(yōu)勢了?
看看比較,這是平常的定義:
def de(x):
return 2 * x+1
print(de(2))
zai看看lambda:
a = lambda x: 2 * x + 1
print(a(2))
我們可以看到lambde的語法風(fēng)格非常的簡潔
lambda 關(guān)鍵字 冒號左邊是參數(shù),多個參數(shù)可以用逗號隔開;冒號右邊是表達(dá)式,lambda返回的是函數(shù)表達(dá)式,所以要把它賦給一個變量,那么變量就相當(dāng)于一個函數(shù)名
演示一個多參數(shù)的lambda:
f = lambda x,y: x + y
print(f(3,4))
#7
看上去好像是沒用,因為我們現(xiàn)在遇不到使用它的場景:
1、python在編寫一些執(zhí)行腳本的時候可以使用lambda,這樣可以接受定義函數(shù)的過程,比如寫一個簡單的腳本管理服務(wù)器。
2、對于一些比較抽象,并且整個程序執(zhí)行下來只需要調(diào)用一兩次的,有時候給函數(shù)起名實在想不出來的。
3、簡化代碼的可讀性。
1、filter()
它是一個內(nèi)建函數(shù)過濾器,就是一個BIF
第一個參數(shù)是一個函數(shù)也可以是一個None,如果是None的話會把值為True的選取出來;如果是函數(shù)的話,就把第二個參數(shù)傳遞進(jìn)去計算,第二個參數(shù)應(yīng)該是一個可迭代的序列;
我們可以用help()來查看它
print(help(filter))
#Help on class filter in module builtins:
看看我們使用的例子:
temp = filter(None,[2,0,False,True])
print(list(temp)
#[1, 2, True]
def odd(x):
return x % 2
temp = filter(odd,range(10))
print(list(temp))
#[1, 3, 5, 7, 9]
print(set(filter(lambda x: x % 2,range(10))))
#{1, 3, 5, 7, 9}
2、map()
map()了在這里不是地圖的意思,在編程領(lǐng)域,map一般作“映射”來解釋。map()這個內(nèi)置數(shù)也有兩個參數(shù),和filter()相似,但是它的第一個參數(shù)只能是函數(shù):
print(help(map))
#Help on class map in module builtins:
看下面例子:
print(list(map(lambda x: x * 2 , range(10))))
#[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
【2】遞歸
1、駕馭遞歸
優(yōu)秀的程序員是伯樂,那么遞歸就是千里馬、萬里馬。普通的程序員用迭代,‘天才的程序員’用遞歸。
遞歸的這個概念,是算法的范疇,不是屬于python的東西,但是當(dāng)你掌握了遞歸的技巧和思路后,你會發(fā)現(xiàn)這是一個非常棒的編程思路。
那么遞歸在我們?nèi)粘I钪杏心切├樱?/p>
謝爾賓斯基三角形: [圖片上傳失敗...(image-b1d940-1521459461499)]
其實它就是一個樹形的結(jié)構(gòu):
[圖片上傳失敗...(image-c655cf-1521459461499)]
說了這么多,遞歸的概念就是自身調(diào)用自身。
def mydef(x):
return mydef(x+1)
print(mydef(1))
#RecursionError: maximum recursion depth exceeded
嘗試這個例子,很多人都會出現(xiàn)這個錯誤,從理論上來說,這個程序會永遠(yuǎn)的執(zhí)行下去,一直耗盡內(nèi)存資源。但是python3出于保護(hù)機制,默認(rèn)的遞歸深度是100次,所以你的代碼才會停下來。不過你寫寫網(wǎng)絡(luò)爬蟲等其它工具,可能會爬的很深,那么可以自己設(shè)置遞歸的深度,如下:
import sys
sys.setrecursionlimit(10000)
#設(shè)置最大深度為一萬層
2、寫一個求階乘的函數(shù)
整數(shù)階乘是指10 * 9 * 8 * 7.....* 1:
我們用迭代函數(shù)寫一個:
def mydef(n):
res = n
for i in range(1,n):
res *= i
return res
print(mydef(5))
#120
用遞歸寫一個:
def myrecursion(n):
if n ==1:
return 1;
else:
return n * myrecursion(n-1)
print(myrecursion(5))
#120
這個例子遞歸滿足了兩個條件:
(1)調(diào)用函數(shù)本身
(2)設(shè)置了正確的返回值
myrecursion(5) = 5 * myrecursion(4)
myrecursion(4) = 4 * myrecursion(3)
myrecursion(3) = 5 * myrecursion(2)
myrecursion(2) = 5 * myrecursion(1)
myrecursion(1) = 1
最后鄭重的說一下,不是說會使用遞歸,把所用可以迭代的東西用遞歸來替代,真的這樣作就是烏龜程序員了。為什么這么說,遞歸是自己調(diào)用自己,每次函數(shù)調(diào)用都需要進(jìn)行壓棧、彈棧、保存和恢復(fù)寄存器的棧操作,所以非常的耗費時間和空間。
另外,如果遞歸一旦忘記了返回,或者錯誤的設(shè)置了返回條件,那么執(zhí)行這樣的遞歸代碼,就會是一個無底洞:只進(jìn)不出!,所以遞歸,有來有回。
3、斐波那契數(shù)列
斐波那契數(shù)列(Fibonacci sequence),又稱黃金分割數(shù)列、因數(shù)學(xué)家列昂納多·斐波那契(Leonardoda Fibonacci)以兔子繁殖為例子而引入,故又稱為“兔子數(shù)列”,指的是這樣一個數(shù)列:1、1、2、3、5、8、13、21、34、……在數(shù)學(xué)上,斐波納契數(shù)列以如下被以遞歸的方法定義:F(0)=0,F(xiàn)(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)在現(xiàn)代物理、準(zhǔn)晶體結(jié)構(gòu)、化學(xué)等領(lǐng)域,斐波納契數(shù)列都有直接的應(yīng)用,為此,美國數(shù)學(xué)會從1963起出版了以《斐波納契數(shù)列季刊》為名的一份數(shù)學(xué)雜志,用于專門刊載這方面的研究成果。
下面是他講過的故事,如果兔子在出生兩個月后就有繁殖能力,在擁有繁殖能力后,這對兔子每個月能生出一對兔子來,假設(shè)所有兔子都不會死去,那么一年后能有多少對兔子?
| 所有經(jīng)過的月數(shù) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 兔子的總對數(shù) | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 55 | 89 | 144 |
數(shù)學(xué)中的定義:
| 1, 當(dāng) n = 1 時
F(n) = | 1, 當(dāng) n = 2 時
| F(n-1)+F(n-2) 當(dāng) n > 2 時
假設(shè)現(xiàn)在我們經(jīng)歷了20個月,現(xiàn)在有多少對:
(1)迭代實現(xiàn):
def fab(n): #n代表月數(shù)
a1 = 1 #代表這個月現(xiàn)有對數(shù)
a2 = 1 #代表下個月要出生的對數(shù)
a3 = 1 #代表這個月總對數(shù)
if n <1:
print('輸入有誤')
return -1
while (n - 2) > 0:
a3 = a1 + a2
a1 = a2
a2 = a3
n -= 1
return a3
result = fab(20)
if result != -1:
print('總共有 % d對小兔崽子誕生!' % result)
#總共有 6765對小兔崽子誕生!
(2)遞歸實現(xiàn)。
def fab(n):
if n < 1:
print('輸入有誤!')
return -1
if n ==1 or n ==2:
return 1
else:
return fab(n-1) + fab(n-2)
result = fab(20)
if result != -1:
print('總共有 % d對小兔崽子誕生!' % result)
#總共有 6765對小兔崽子誕生!
上面的例子看,遞歸實現(xiàn)起來要簡單一些,但是說過了遞歸消耗內(nèi)存,消耗cpu,效率低,我們試試35個月的遞歸和迭代。
如果你想測試你的電腦牛不牛逼,不妨用遞歸試試。
4、漢諾塔(課堂小練習(xí))
漢諾塔:漢諾塔(又稱河內(nèi)塔)問題是源于印度一個古老傳說的益智玩具。大梵天創(chuàng)造世界的時候做了三根金剛石柱子,在一根柱子上從下往上按照大小順序摞著64片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上。并且規(guī)定,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤。
漢羅塔游戲:
[圖片上傳失敗...(image-3a3cb0-1521459461499)]
三根柱子分別是: X Y Z
對于游戲的玩法:
(1)將前63個盤子從X移動到Y(jié)上,確保大盤在小盤下。
(2)將最底下的第64個盤子從X移動到Z。
(3)將Y上的63個盤子移動到Z上。
思路如下:
最底下的盤子我們給它編號第64個,最上面的給編號第1個。
def hanoi(n,x,y,z):
if n == 1:
print(x,'-->',z) #如果只有一層就直接x移動到z
else:
hanoi(n-1,x,z,y) #將n-1的盤子從x移動到y(tǒng)上
print(x,'-->',z) #將最底下的第64個盤子從x移動到z
hanoi(n-1,y,x,z) #將Y上的63個盤子移動到z上
hanoi(64,'X','Y','Z')
這就是遞歸的魔力