字幕列表 影片播放 列印所有字幕 列印翻譯字幕 列印英文字幕 In order to understand recursion, you must 為了瞭解遞迴,你必須 first understand recursion. 先瞭解遞迴 Having recursion in program design means that you have self-referential definitions. 在程式中有遞迴意味著程式裏有自我參考的定義 Recursive data structures, for instance, are data structures that include themselves in their definitions. 具有遞迴形式的資料結構,指的是在定義裏就包含本身的資料結構 But today, we're going to focus on recursive functions. 但目前我們打算專注在遞迴函式 Recall that functions take inputs, arguments, and return a value as their output represented by this diagram here. 你應該還記得這張圖:函式需要輸入若干個參數,並傳回一個值作為輸出 We'll think of the box as the body of the function, 我們將這個盒子想成是函式的本身 containing the set of instructions that interpret the input and provide an output. 其中包含一組指令,用來詮釋從輸入到輸出之間所發生的事 Taking a closer look inside the body of the function could reveal calls to other functions as well. 仔細看看函式本身,在裏面是如何呼叫其他的函式 Take this simple function, foo, that takes a single string as input and 以這個名字叫作foo的函式為例,它需要一個字串作為輸入 prints how many letters that string has. 然後印出這個字串裏有多少個字母(函式foo的功能為印出字串長度) The function strlen, for string length, is called, whose output is 我們用strlen函式計算字串的長度 required for the call to printf. 然後再呼叫printf函式將字串的長度輸出 Now, what makes a recursive function special is that it calls itself. 遞迴函式特別之處,就是它會呼叫自己 We can represent this recursive call with this orange arrow looping back to itself. 在這裏我們用指向本身的橙色箭頭表示遞迴呼叫 But executing itself again will only make another recursive call, and 遞迴呼叫引發另一次遞迴呼叫,這個過程會一而再、再而三地重覆 another and another. 沒完沒了 But recursive functions can't be infinite. 但是總不能這樣無窮無盡遞迴下去 They have to end somehow, or your program will run forever. 肯定要有個方法來終止遞迴,不然程式會執行到天荒地老 So we need to find a way to break out of the recursive calls. 我們得找出停止遞迴呼叫的方法 We call this the base case. 就是基礎條件 When the base case condition is met, the function returns without making another recursive call. 當滿足基礎條件的要求時,遞迴呼叫就終止了 Take this function, hi, a void function that takes an int n as input. 以這個名字為hi的函式為例,它接受整數n作為參數 The base case comes first. 首先驗證基底條件 If n is less than zero, print bye and return For all other cases, the 如果n小於0,則函式印出bye之後就結束了。除此之外 function will print hi and execute the recursive call. 函式在印出hi之後繼續遞迴呼叫 Another call to the function hi with a decremented input value. 將參數n減去1之後再次呼叫hi函式 Now, even though we print hi, the function won't terminate until we 即使函式在印出hi之後,仍然會繼續執行 return its return type, in this case void. 在這個例子裏,函式回傳的型別為void So for every n other than the base case, this function hi will return hi of n minus 1. 除了滿足基礎條件的n值之外,函式會傳回將n值減1之後再次呼叫函式hi的結果 Since this function is void though, we won't explicitly type return here. 由於這個函式的型別是void,它不會傳回任何值 We'll just execute the function. 我們來執行這個函式 So calling hi(3) will print hi and execute hi(2) which executes hi(1) one 呼叫hi(3)在印出hi之後,接著執行hi(2),再接著執行hi(1) which executes hi(0), where the base case condition is met. 接下來在執行hi(0)的時候,基礎條件被滿足了 So hi(0) prints bye and returns. 所以在hi(0)印出bye之後,遞迴過程就結束了 OK. 太好了 So now that we understand the basics of recursive functions, that they need 現在我們瞭解遞迴函式的概念了 at least one base case as well as a recursive call, let's move on to a 在遞迴呼叫之外,至少要存在一個基礎條件。我們繼續 more meaningful example. 更有意思的例子 One that doesn't just return void no matter what. 一個不僅只是傳回void的函式 Let's take a look at the factorial operation used most commonly in probability calculations. 我們來看看在機率計算中常會用到的階乘 Factorial of n is the product of every positive integer less than and equal to n. n階乘是所有小於或等於n的整數的乘積 So factorial five is 5 times 4 times 3 times 2 times 1 to give 120. 所以5階乘是5x4x3x2x1=120 Four factorial is 4 times 3 times 2 times 1 to give 24. 4階乘是4x3x2x1=24 And the same rule applies to any positive integer. 同樣的規則可以套用到任意正整數 So how might we write a recursive function that calculates the factorial of a number? 我們如何編寫計算某個整數階乘的遞迴函式呢? Well, we'll need to identify both the base case and the recursive call. 我們來釐清基礎條件和遞迴呼叫 The recursive call will be the same for all cases except for the base 除了基礎條件之外,所有的遞迴呼叫長的一模一樣 case, which means that we'll have to find a pattern that will give us our desired result. 意謂著我們得去找出求解的規律 For this example, see how 5 factorial involves multiplying 4 by 3 by 2 by 1 以5階乘為例,它包含乘4、乘3、乘2、乘1等等的運算 And that very same multiplication is found here, the 相同的運算也發生在 definition of 4 factorial. 4階乘的定義上 So we see that 5 factorial is just 5 times 4 factorial. 我們看到5階乘也不過就是5再乘上4階乘罷了 Now does this pattern apply to 4 factorial as well? 同樣的概念如何套用到4階乘呢? Yes 是的 We see that 4 factorial contains the multiplication 3 times 2 times 1, the 我們看到4階乘就是乘3、乘2、乘1 very same definition as 3 factorial. 亦即乘於3階乘的定義 So 4 factorial is equal to 4 times 3 factorial, and so on and so forth our 所以4階乘就是4再乘上3階乘,依此類推 pattern sticks until 1 factorial, which by definition is equal to 1. 一直來到1階乘,根據定義,1階乘就是1 There's no other positive integers left. 所有的正整數都處理完了 So we have the pattern for our recursive call. 現在我們有了階乘的遞迴呼叫的規律了 n factorial is equal to n times the factorial of n minus 1. n階乘等於n乘上n減1階乘 And our base case? 那麼基礎條件呢? That'll just be our definition of 1 factorial. 就是1階乘的定義 So now we can move on to writing code for the function. 我們現在可以為這個函式編寫程式碼了 For the base case, we'll have the condition n equals equals 1, where 當n值等於1的時候,基礎條件成立了 we'll return 1. 函式回傳1 Then moving onto the recursive call, we'll return n times the 否則就繼續執行遞迴呼叫,函式回傳n再乘於 factorial of n minus 1. n減去1的階乘 Now let's test this our. 現在測試我們的成果 Let's try factorial 4. 試試看4階乘 Per our function it's equal to 4 times factorial(3). 在程式中,4階乘等於4乘上3階乘 Factorial(3) is equal to 3 times factorial(2). 3階乘等於3乘上2階乘 Factorial(2) is equal to 2 times factorial(1), which returns 1. 2階乘等於2乘上1階乘,1階乘就是1了 Factorial(2) now returns 2 times 1, 2. 2階乘回傳2乘1,這得到2 Factorial(3) can now return 3 times 2, 6. 3階乘回傳3乘2,這得到6 And finally, factorial(4) returns 4 times 6, 24. 最後,4階乘回傳4乘6,這等於24 If you're encountering any difficulty with the recursive call, pretend that 如果在遞迴呼叫時遇到任何因難,就想像 the function works already. 函式是可以正常運作的 What I mean by this is that you should trust your recursive calls to return 我的意思是:你一定要相信遞迴呼叫所傳回的 the right values. 是正確的結果 For instance, if I know that factorial(5) equals 5 times 以5階乘為例,它等於5乘上4階乘 factorial(4), I'm going to trust that factorial(4) will give me 24. 我會相信函式factorial(4)將會傳回24 Think of it as a variable, if you will, as if you already defined factorial(4). 就把factorial(4)想成是我們已經定義好的變數 So for any factorial(n), it's the product of n and the previous factorial. 所以對於任一個 factorial(n)而言,它的值就是n和前一個階乘的積 And this previous factorial is obtained by calling factorial of n minus 1. 而前一個階乘可以經由呼叫factorial(n-1)而得 Now, see if you can implement a recursive function yourself. 現在來看看你能不能實作屬於自己的遞迴函式 Load up your terminal, or run.cs50.net, and write a function sum 打開你的瀏覽器,在網址列鍵入run.cs50.net,寫一個求總和的函式 that takes an integer n and returns the sum of all consecutive positive integers from n to 1. 這個函式計算1連加到n的總和 I've written out the sums of some values to help you our. 我已經為你寫下若干數值的和 First, figure out the base case condition. 首先要找出基礎條件 Then, look at sum(5). 接著看看sum(5) Can you express it in terms of another sum? 你能不能用其他項目的和來表達sum(5)? Now, what about sum(4)? 那麼sum(4)呢? How can you express sum(4) in terms of another sum? 你如何其他項目的和來表達sum(4)呢? Once you have sum(5) and sum(4) expressed in terms of other sums, see 一旦你可以用其他項目的和來表達sum(5)及sum(4) if you can identify a pattern for sum(n). 看看你是否能找到計算sum(n)的通式 If not, try a few other numbers and express their sums in terms of another numbers. 無果還是沒辦法的話,再嘗試其他的數值是不是能用另外的項目的和來表達 By identifying patterns for discrete numbers, you're well on your way to identifying the pattern for any n. 嘗試過幾個數值之後,你應該可以順利找到解答任意整數n的通式 Recursion's a really powerful tool, so of course it's not limited to 遞迴實在是太強大了,當然不限於 mathematical functions. 數學函式 Recursion can be used very effectively when dealing with trees for instance. 比如說遞迴可以非常有效率地處理樹的問題(樹是一種重要的資料結構) Check out the short on trees for a more thorough review, but for now 很快地看一下樹的結構 recall that binary search trees, in particular, are made up of nodes, each 二元搜尋樹由節點組成 with a value and two node pointers. 每個節點擁有自己的數值、和兩個指到其他節點的指標 Usually, this is represented by the parent node having one line pointing 父節點會各以一條線,連接到 to the left child node and one to the right child node. 左子樹和右子樹 The structure of a binary search tree lends itself well 二元樹的結構使得它本身非常適合應用在 to a recursive search. 遞迴搜尋 The recursive call either passes in the left or the right node, but more of that in the tree short. 要嘛就是以遞迴呼的方式處理左子樹、不然就是以遞迴呼的方式處理右子樹 Say you want to perform an operation on every single node in a binary tree. 假設你想對二元樹的每一個節點進行運算 How might you go about that? 你會怎麼做呢? Well, you could write a recursive function that performs the operation 你可能寫一個遞迴函式來作到這件事 on the parent node and makes a recursive call to the same function, passing in the left and right child nodes. 先對父節點運算,然後再分別對它的左子樹和右子樹進行遞迴呼叫 For example, this function, foo, that changes the value of a given node and 以函式foo為例,它會將所指定的節點及 all of its children to 1. 子樹上的所有節點的值設為1 The base case of a null node causes the function to return, indicating 當遇到空節點時,基礎條件就成立了,這使得遞迴呼叫結束 that there aren't any nodes left in that sub-tree. 這表示在這棵子樹上已經沒有其他的節點需要被處理 Let's walk through it. 我們來演算一次 The first parent is 13. 所給予的節點的值是13 We change the value to 1, and then call our function on the left as well as the right. 我們把13改成1,然後分別為左子樹和右子樹呼叫函式 The function, foo, is called on the left sub-tree first, so the left node 先為左子樹呼叫函式foo,於是左節點的值 will be reassigned to 1 and foo will be called on that node's children, 被設定成1,接著再為這個節點的所有子樹呼叫函式foo first the left and then the right, and so on and so forth. 先是左子樹,再來是右子樹…依此類推 And tell them that branches don't have any more children so the same process 當我們發現某個節點已經沒有任何子樹時,相同的過程 will continue for the right children until the whole tree's nodes are reassigned to 1. 將套用到右子樹,一直到整棵樹所有的節點被設成1 As you can see, you aren't limited to just one recursive call. 如你所見,遞迴呼叫的威力不限於此 Just as many as will get the job done. 遞迴可以完成更多事 What if you had a tree where each node had three children, 如果你有一棵三元樹呢? Left, middle, and right? 左子樹、中子樹、和右子樹? How would you edit foo? 你要怎麼修改函式foo? Well, simple. 這很簡單 Just add another recursive call and pass in the middle node pointer. 只要對中子樹加入另一個遞迴呼叫就可以了 Recursion is very powerful and not to mention elegant, but it can be a 遞迴十分強大、而且非常優雅,但它可能 difficult concept at first, so be patient and take your time. 很難一開始就被理解,要有耐心,慢慢來 Start with the base case. 從基礎條件開始說起 It's usually the easiest to identify, and then you can work 基礎條件通常很容易推導,你可以從這裏 backwards from there. 倒著推導回去 You know you need to reach your base case, so that might give you a few hints. 當你需要推導基礎條件時,這樣作或許可以帶來靈感 Try to express one specific case in terms of other cases, or in sub-sets. 試試看使用其他的條件來表示此一特殊條件 Thanks for watching this short. 謝謝觀賞 At the very least, now you can understand jokes like this. 現在你起碼聽得懂像這樣的笑話 My name is Zamyla, and this is cs50. 我是Zamyla,課程代號為cs50 Take this function, hi, a void function that takes an int, n, as input. 以函式hi為例,這是型別為void的函式,它接收整數n為參數 The base case comes first. 先來看看基礎條件 If n is less than 0, print "bye" and return. 若n值小於0,則印出bye之後,函式就結束了