不能忘記!數(shù)據(jù)科學(xué)家面試時(shí)應(yīng)該掌握的3個(gè)編程概念
算法是數(shù)據(jù)科學(xué)不可分割的一部分。雖然很多數(shù)據(jù)科學(xué)家在學(xué)習(xí)的時(shí)候沒有選修合適的算法課程,但它確實(shí)很重要。
比如說,許多公司在面試數(shù)據(jù)科學(xué)家時(shí),都會問到數(shù)據(jù)結(jié)構(gòu)和算法。
那么,現(xiàn)在問題是,問數(shù)據(jù)科學(xué)家這樣的問題到底有什么用。
對于這個(gè)問題,我的答案是,數(shù)據(jù)結(jié)構(gòu)問題可以被當(dāng)作是對編碼能力的測試。
我們都在人生的不同階段接受過能力測試,但是這些測試并不能完美地評判一個(gè)人,幾乎沒有什么測試能做到這一點(diǎn)。
那么,為什么不用一個(gè)標(biāo)準(zhǔn)算法測試來評判一個(gè)人的編碼能力呢?
因?yàn)樗惴ê蜏y試都并非死板,你總能在前者總結(jié)的基礎(chǔ)上不斷改進(jìn)、創(chuàng)新,總能運(yùn)用不同的算法來回應(yīng)這些測試。
但這也并不意味著你可以不掌握基礎(chǔ)就可以肆意動(dòng)搖算法的地基。
牢固掌握基礎(chǔ)算法的概念和運(yùn)用,永遠(yuǎn)是一個(gè)優(yōu)秀數(shù)據(jù)科學(xué)家必備的素質(zhì)。
本文將以一種容易理解的方式幫助數(shù)據(jù)科學(xué)家快速回顧相關(guān)研究并介紹一些基本算法概念。
1. 遞歸/記憶化
遞歸是將定義的函數(shù)應(yīng)用到自己的定義中。簡而言之,遞歸是函數(shù)的自身調(diào)用。當(dāng)你用谷歌搜索遞歸時(shí),會遇到點(diǎn)小插曲。
剛開始學(xué)習(xí)數(shù)據(jù)科學(xué)的人可能會覺得遞歸有點(diǎn)難,但其實(shí)它很容易理解。一旦理解了,就會發(fā)現(xiàn)這是一個(gè)很棒的概念。
解釋遞歸的最好例子就是計(jì)算一個(gè)數(shù)的階乘。
- def factorial(n):
- if n==0:
- return 1
- return n*factorial(n-1)
可以很容易地看出階乘是一個(gè)遞歸函數(shù)。
- Factorial(n) = n*Factorial(n-1)
那么如何將它轉(zhuǎn)化為編程呢?
遞歸調(diào)用的函數(shù)通常由兩部分組成:
- 基線條件——終止遞歸的條件。
- 遞歸公式——向基線條件發(fā)展的公式。
許多問題解決到最后,就是遞歸問題。這也適用于數(shù)據(jù)科學(xué)。
例如,一棵決策樹是二叉樹,樹算法通常是遞歸的。或者,以我們經(jīng)常使用的排序?yàn)槔?。排序的算法稱為歸并排序(mergesort),它本身就是一種遞歸算法。另一種是對分查找 (binary search),包括在數(shù)組中查找元素。
現(xiàn)在我們了解了遞歸的基本知識,接下來試著找到第n個(gè)斐波那契數(shù)。斐波那契數(shù)列是一系列數(shù)字,其中每個(gè)數(shù)字(斐波那契數(shù))都是前面兩個(gè)數(shù)字之和。最簡單的數(shù)列是1、1、2、3、5、8等。要找到第n個(gè)斐波那契數(shù),可以用如下代碼:
- def fib(n):
- if n<=1:
- return 1
- return fib(n-1) + fib(n-2)
但是,發(fā)現(xiàn)問題了嗎?
如果試圖計(jì)算fib(n=7)要運(yùn)行兩次fib(5) ,運(yùn)行三次 fib(4) ,運(yùn)行5次 fib(3)。隨著n越來越大,對同一數(shù)字進(jìn)行了多次調(diào)用,遞歸函數(shù)對其進(jìn)行了一次又一次的計(jì)算。
稍微改變我們的執(zhí)行,添加一個(gè)詞典來為這個(gè)方法添加一些存儲。現(xiàn)在,每次計(jì)算出一個(gè)數(shù)字,這個(gè)備忘錄詞典就會更新。如果再出現(xiàn)同樣的數(shù)字,就不需要再對它進(jìn)行計(jì)算,而是可以直接從備忘錄字典中得出結(jié)果。這種增加存儲的方式被稱為記憶化。
- memo = {}def fib_memo(n): if n in memo: return memo[n] if n<=1: memo[n]=1 return 1 memo[n] = fib_memo(n-1) +fib_memo(n-2) return memo[n]
通常,我會先寫遞歸代碼,如果它重復(fù)調(diào)用相同的參數(shù),我會再添加一個(gè)詞典來進(jìn)行記憶化。
這幫助大嗎?
圖中是不同n值的運(yùn)行時(shí)間的比較。可以看到:沒有記憶化的斐波那契函數(shù)運(yùn)行時(shí)間呈指數(shù)性增長,而記憶化函數(shù)的運(yùn)行時(shí)間則是線性的。
2. 動(dòng)態(tài)規(guī)劃
遞歸本質(zhì)就是一種自上而下的方法。比如在計(jì)算斐波那契數(shù)n時(shí),就是從n開始,然后對n-2和n-1進(jìn)行遞歸調(diào)用,依此類推。
在動(dòng)態(tài)規(guī)劃中,我們采取自下而上的方法。這本質(zhì)上是一種迭代編寫遞歸的方法。首先計(jì)算fib(0)和fib(1),然后使用前面的結(jié)果生成新的結(jié)果。
- def fib_dp(n):
- dp_sols = {0:1,1:1}
- for i in range(2,n+1):
- dp_sols[i] = dp_sols[i-1] +dp_sols[i-2]
- return dp_sols[n]
上圖是“動(dòng)態(tài)規(guī)劃”和“記憶化”運(yùn)行時(shí)間的比較??梢钥闯鏊鼈兌际蔷€性的,但動(dòng)態(tài)規(guī)劃更快一點(diǎn)。
為什么呢?因?yàn)樵谶@種情況下,動(dòng)態(tài)規(guī)劃只對每個(gè)子問題進(jìn)行一次調(diào)用。
關(guān)于開發(fā)動(dòng)態(tài)規(guī)劃的貝爾曼是如何選定“動(dòng)態(tài)規(guī)劃”這個(gè)名字的,有一個(gè)很有趣的故事:
“動(dòng)態(tài)規(guī)劃”這個(gè)名字是從哪里來的呢?20世紀(jì)50年代的數(shù)學(xué)研究情況并不是很好。當(dāng)時(shí),華盛頓有一位非常有趣的紳士,名叫威爾遜,他是國防部長。實(shí)際上,他對研究這個(gè)詞懷有病態(tài)的恐懼和仇恨。那我能起個(gè)什么名字呢?首先,我考慮了“計(jì)劃”、“決策”、“思考”。但是出于各種原因,“計(jì)劃”并不是一個(gè)好詞。于是,我決定使用“規(guī)劃”一詞。我想傳遞的概念是,這是動(dòng)態(tài)的,多級的,并且是時(shí)變的。因此,我認(rèn)為動(dòng)態(tài)規(guī)劃是個(gè)好名字,這可以一舉兩得。這是連國會議員都不會反對的。所以我決定采用這個(gè)名字。
3. 二進(jìn)制搜索
假設(shè)有一個(gè)排序了的數(shù)字組合,要從這個(gè)數(shù)組中找出一個(gè)數(shù)字。我們可以采用線性搜索,挨個(gè)地檢查每一個(gè)數(shù)字,直到找到特定數(shù)字。問題是,如果數(shù)組包含數(shù)百萬個(gè)元素,這種方法就需要花費(fèi)很長時(shí)間。這種情況下,可以采用二進(jìn)制搜索方法。
來源:發(fā)現(xiàn)37-海洋中有3.7萬億條魚,他們正在尋找其中的1條
- # Returns index of target innums array if present, else -1
- def binary_search(nums, left, right, target):
- # Base case
- if right >= left:
- mid = int((left + right)/2)
- # If target is present at themid, return
- if nums[mid] == target:
- return mid
- # Target is smaller than midsearch the elements in left
- elif nums[mid] > target:
- return binary_search(nums,left, mid-1, target)
- # Target is larger than mid,search the elements in right
- else:
- return binary_search(nums,mid+1, right, target)
- else:
- # Target is not in nums
- return -1nums =[1,2,3,4,5,6,7,8,9]
- print(binary_search(nums, 0, len(nums)-1,7))
這是一個(gè)基于遞歸算法的高級例子。利用數(shù)組被排序的事實(shí),遞歸地查看中間元素,看看是想在中間元素的左邊還是右邊搜索。這使得每一步的搜索空間減少2倍。
因此,二進(jìn)制搜索算法的運(yùn)行時(shí)間是O(logn),而不是線性搜索的O(n)。
這有什么作用呢?下面是運(yùn)行時(shí)間的比較。我們可以看到二進(jìn)制搜索與線性搜索相比是相當(dāng)快。
當(dāng)n=10000時(shí), 二進(jìn)制搜索需要大約13步,線性搜索需要10000步。
結(jié)論
本文談到了一些構(gòu)成編程基礎(chǔ)的算法,雖然是基礎(chǔ),但也同樣令人興奮。
數(shù)據(jù)科學(xué)家在面試時(shí)會經(jīng)常被問到這些算法,深入理解它們可能會幫助你找到理想的工作哦。
盡管你不學(xué)習(xí)這些算法也可以在數(shù)據(jù)科學(xué)中取得進(jìn)步,但你可以把學(xué)習(xí)它們當(dāng)作一種樂趣,培養(yǎng)興趣的同時(shí)還可以順便提高編程技能。
一舉兩得,何樂而不為呢?