<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          遞歸 —— 你值得擁有

          共 4460字,需瀏覽 9分鐘

           ·

          2019-12-20 23:22


          本文公眾號來源:編程新說作者:編程新說李新杰本文已收錄至我的GitHub


          相信很多人對遞歸的認(rèn)知是這樣的:

          function?foo()?{
          ????foo();
          }


          就是一個函數(shù)在它內(nèi)部又調(diào)用了自己,簡稱自我調(diào)用


          刷新對遞歸的認(rèn)知


          如果遇到一個問題,你說你可以用遞歸解決,基本上大家都會覺得這不是一個最好的方案。

          如果另一個人說,他不用遞歸就可以搞定了,基本上大家都會認(rèn)為他的方法比你的牛逼些。

          怎么說呢,就是大部分人可能對遞歸都是有點“偏見”的,或多或少罷了。

          我想這可能和遞歸的執(zhí)行過程有關(guān),一個函數(shù)在還沒有執(zhí)行完時又調(diào)用了自己,這就需要保存函數(shù)調(diào)用的當(dāng)前上下文,然后發(fā)起一個新的函數(shù)調(diào)用。

          結(jié)果又是這樣子,又是在函數(shù)還沒有執(zhí)行完時再次調(diào)用了自己,那就需要繼續(xù)保存本次函數(shù)調(diào)用的上下文,然后再發(fā)起一次新的函數(shù)調(diào)用。

          如此這般下去,會造成調(diào)用層次嵌套太深,保存的函數(shù)調(diào)用上下文過多,然后把線程棧空間用完了,最后就是StackOverflow了。

          這是事實,任何人都無法辯解,自然我也不例外。但我還是建議那些對遞歸不太喜歡的開發(fā)人員要適當(dāng)?shù)母淖冏约旱倪@個看法。

          備注:尾遞歸可以避免這個問題。

          把遞歸看作是函數(shù)的自我調(diào)用,是一種十分狹隘的思想。我們應(yīng)該提高自己的認(rèn)知,推而廣之的來看遞歸,就會發(fā)現(xiàn):

          遞歸就是某種形式的不斷重復(fù),結(jié)果構(gòu)成了閉環(huán)。(當(dāng)然,最后是可以跳出來的。)

          按照這個觀點去思考,你會發(fā)現(xiàn)我們生活在遞歸中。不相信嗎?一起快速看看吧。

          2019的春夏秋冬即將過完,馬上就又迎來了2020的春夏秋冬,一年四季的變換無窮無盡何時休。這是不是遞歸?當(dāng)然是啦。

          還有每天24小時的晝夜交替永不停歇。還有每天上班打卡下班回家,天天如此仿佛看不到盡頭。還有老子生兒子,兒子生孫子,世世代代的生老病死。

          還有電磁波的傳播,就是變化的電場產(chǎn)生了磁場,變化的磁場又產(chǎn)生了電場,電場和磁場相互交替著向前傳播。

          以上這些都可以認(rèn)為是遞歸。所以不要只看表明現(xiàn)象,要看事物的本質(zhì)是不是某種形式的重復(fù),且形成了閉環(huán),最后在適當(dāng)?shù)臈l件下又跳出了這個環(huán)。

          總之,接受遞歸,將會擁有一片更為廣闊的天地。


          識別出構(gòu)成遞歸的要素


          有些遞歸很明顯,一眼就看出來了。有些則很含蓄,需要仔細(xì)甄別才行。這就造成有些簡單、有些很難。

          但是當(dāng)你寫出來后,又發(fā)現(xiàn)其實也沒有那么難,我相信這種感覺應(yīng)該都有過。今天就以科學(xué)的分析方法來搞一把,看看結(jié)論是什么?

          根據(jù)上一小節(jié)的描述,我們知道遞歸必然是一種重復(fù),而且還有跳出這種重復(fù)的條件。

          因此,我們認(rèn)為遞歸至少包含兩個要素:重復(fù)體跳出條件

          結(jié)合一個最簡單的示例來檢驗一下。比如,求N個自然數(shù)的和。就是


          n + (n - 1) + (n - 2) + ... + 2 + 1


          這樣一個表達(dá)式的值。

          我們很容易寫出一個遞歸來:

          function?sum(n)?{
          ????if?(n?==?1)?{
          ????????return?1;
          ????}
          ????return?n?+?sum(n?-?1);
          }


          不難看出這里的重復(fù)體就是sum()函數(shù)自身,因為它一直在自我調(diào)用。而跳出條件也十分明顯,就是n == 1

          這種最簡單形式的遞歸是符合我們剛剛提出的觀點的,再來看另一個稍微復(fù)雜點的情況。

          假如在草原上遇到一群動物在奔跑,普遍人就認(rèn)為這可能就是一次普通的集體活動而已,但是專家可能識別出它們是在集體遷徙。

          這區(qū)別是很大的,遷徙的話就是從A地到B地,等來年某個時候再從B地回到A地,這不就是遞歸嘛,而普通活動則肯定不是遞歸。

          這里的問題是為什么“專家”能夠看出是遞歸,而我們普通人看不出呢?這是因為參與遞歸的A地和B地相距太遠(yuǎn),我們?nèi)狈I(yè)知識,識別不出來。

          根據(jù)這個,我們就又可以得出兩個要素:遞歸中的重復(fù)體可以是兩個這兩個重復(fù)體互相調(diào)用的條件

          我們還使用求自然數(shù)和的例子,但是加一個限制條件,奇數(shù)和偶數(shù)分別用兩個函數(shù)來處理。

          下面代碼僅僅用作示例,可能沒有什么實際意義:

          function?oddSum(n)?{
          ????if?(n?==?1)?{
          ????????return?1;
          ????}
          ????if?(n?%?2?==?1)?{
          ????????return?n?+?evenSum(n?-?1);
          ????}
          ????return?evenSum(n);
          }


          function?evenSum(n)?{
          ????if?(n?==?2)?{
          ????????return?2;
          ????}
          ????if?(n?%?2?==?0)?{
          ????????return?n?+?oddSum(n?-?1);
          ????}
          ????return?oddSum(n);
          }


          不難看出oddSum()evenSum()這兩個函數(shù)都是重復(fù)體,它們互調(diào)對方,合起來構(gòu)成遞歸。互調(diào)對方的條件就是n是奇數(shù)還是偶數(shù),跳出條件就是n是1或是2

          既然重復(fù)體可以有兩個,那么就可以有三個甚至更多,不過原理都是一樣的。因此我們可以得出遞歸三要素:

          1)識別出重復(fù)體,可能是多個。

          2)如果是多個,識別出重復(fù)體間的互調(diào)和自調(diào)條件。

          3)識別出跳出條件,以便結(jié)束。

          通俗的講就是,重復(fù)體越多越復(fù)雜,它們既可以互相調(diào)用也可以自我調(diào)用,一個重復(fù)體可以調(diào)多個重復(fù)體,多個重復(fù)體也可以調(diào)用一個重復(fù)體。

          而且會有很多調(diào)用條件,只有在條件滿足某個重復(fù)體的時候才會發(fā)起對它的調(diào)用。而且在自我調(diào)用時也要滿足某個條件。

          如果把重復(fù)體看作節(jié)點,把調(diào)用關(guān)系看作邊,十分復(fù)雜的遞歸就會變得像蜘蛛網(wǎng)一樣密密麻麻的。


          小試牛刀not分配律


          利用上面的結(jié)論,再結(jié)合一個相對有意義的案例,來試試看。之前遇到一個邏輯表達(dá)式求值的問題,表達(dá)式由操作數(shù)andornot括號組成。

          一開始為了簡單,就做了個規(guī)定,not只能作用于操作數(shù),不能作用于括號。就是這樣子的:

          not A and not B 是可以的,not (A and B) 是不可以的。

          后來需求有變,確實也需要支持not作用于括號的這種情況。但是我又不想修改解析表達(dá)式的代碼,好像也不太好改。

          因為表達(dá)式字符串轉(zhuǎn)換成表達(dá)式樹之后,括號就沒有了。它本來就是起一個優(yōu)先級的作用,因為樹的節(jié)點本身就帶有優(yōu)先級了。

          關(guān)鍵一開始就沒有這方面的設(shè)計,所以要改的話,改動量較大,后來轉(zhuǎn)念一想,我何不把not分配到括號里面呢,就像這樣:

          not (A and B) -> (not A or not B)

          這樣是完全等價的,而且表達(dá)式解析代碼一點也不用改。

          所以接下來要做的就是,通過純字符串操作not分配到括號里面,然后再解析,我把它稱為not分配律。

          其實這個不用遞歸使用while循環(huán)也可以,只需要自己維護(hù)好進(jìn)入/退出括號這個上下文和對應(yīng)的not情況即可。

          不過這里還是采用遞歸實現(xiàn)。仔細(xì)分析后發(fā)現(xiàn),我們只需處理not后面是左括號的這種情況,如果不是只需原樣不動就行。

          這樣的問題一時不太好想,那就把所有的情況都列出來,找找規(guī)律。

          1)沒有括號沒有not的,A or B

          2)沒有括號有not的,not A or not B

          3)有括號沒有not的,(A or B) and C

          4)有不在括號前的not的,(not A or not B) and not C

          5)有在括號前的not的,not (A or B)

          看完之后發(fā)現(xiàn),其實這里也存在不需要遞歸的情況,比如前四種一個循環(huán)就可以了。第五種有了括號前面的not后就需要遞歸了。

          可能乍一看認(rèn)為也不需要啊,但是要寫成這樣呢:

          not (not (not (A or B)))

          可以很多層嵌套,這回肯定需要了。所以:

          當(dāng)not遇上左括號這種情況就是一個重復(fù)體。調(diào)用這個重復(fù)體的條件自然就是not遇上左括號。

          只有這一個重復(fù)體嗎?我們嘗試把表達(dá)式再嵌套一層看看:

          not (A or not (B or C) and D)

          當(dāng)not分配到括號里后,會與里面括號前面的not抵消掉,這樣里面的括號只需原樣不動就可以,括號結(jié)束后,not繼續(xù)與括號后面的部分進(jìn)行轉(zhuǎn)換。

          所有內(nèi)部括號這部分相當(dāng)于一個獨立的上下文,外層括號是一個上下文,這涉及到從外層上下文進(jìn)入內(nèi)部上下文,然后再退出內(nèi)部上下文回到外層上下文。

          所以內(nèi)部的括號雖然最終沒有not,但也是一個重復(fù)體,于是就有:

          在上面那個重復(fù)體里的括號也是一個重復(fù)體,調(diào)用條件就是在上面那個重復(fù)體里遇到左括號。

          通俗一點說就是帶not的括號里面出現(xiàn)了不帶not的括號。

          還可以再復(fù)雜點,再增加一層嵌套看看:

          not (A or not (B or C and not (D or E)) and F)

          通俗的說就是帶not的括號里是不帶not的括號,該括號里又有了帶not的括號。

          即三層括號嵌套,這樣我們就需要在第二個重復(fù)體里調(diào)用第一個重復(fù)體,調(diào)用條件依然是在第二個重復(fù)體里遇上not加左括號。

          這樣這兩個重復(fù)體之間就形成了互相調(diào)用,調(diào)用條件是在自己的重復(fù)體內(nèi)遇到了括號或not加括號。造成互相調(diào)用的原因就是括號的嵌套和not的存在。

          所以遞歸調(diào)用確實有很多的上下文,這些上下文會自動保存和還原,因此如果采用非遞歸形式的話,這個上下文就需要自己保存和還原了。

          而且有些遞歸非常復(fù)雜時,基本上轉(zhuǎn)變?yōu)榉沁f歸的形式非常困難,有的可能轉(zhuǎn)不成。

          最后總結(jié)一下

          整個過程既有非遞歸部分也有遞歸部分。遞歸部分有兩個重復(fù)體。從非遞歸部分只能進(jìn)入遞歸部分的第一個重復(fù)體,進(jìn)入條件是遇上not加左括號。

          這兩個重復(fù)體可以互相調(diào)用,也可以自己調(diào)自己。調(diào)用條件上面已經(jīng)說的很明白了。這兩個重復(fù)體的退出條件都是遇到右括號。

          當(dāng)所有嵌套調(diào)用的重復(fù)體全部都退出時,遞歸部分就執(zhí)行完畢,接著進(jìn)入非遞歸部分繼續(xù)執(zhí)行,然后還可以再次進(jìn)入遞歸部分,然后再退出。

          直到最后表達(dá)式字符串處理完畢,就全部結(jié)束了。最后再重復(fù)下這句話:

          遞歸確實有一定的難度,但是當(dāng)你寫出來后,發(fā)現(xiàn)也不過如此。


          發(fā)現(xiàn)遞歸四要素理論


          我們發(fā)現(xiàn)在遞歸的三要素上還要再加一個要素,就是重復(fù)體也會有退出條件,控制著某個重復(fù)體的執(zhí)行結(jié)束

          這樣就構(gòu)成了遞歸四要素

          1)識別出重復(fù)體,可能是多個。

          2)如果是多個,識別出重復(fù)體間的互調(diào)和自調(diào)條件。

          3)如果是多個,識別出重復(fù)體的退出條件。

          4)識別出跳出條件,以便結(jié)束。

          個人感覺:

          使用遞歸解決問題,思考起來較難,但代碼寫起來簡單。

          使用while循環(huán)解決問題,思考起來簡單,但代碼寫起來較難。

          自我發(fā)問:

          那么我們什么時候才能跳出自己的遞歸呢?答案是,鬼知道。

          請問鬼是誰?我怎么知道,你去問鬼啊。WTF!

          歡迎加入交流群學(xué)習(xí),備注加群說實話在這個群,哪怕您不說話,光看聊天記錄,都能學(xué)到東西0e44715e252654981cb6512619644fc8.webp

          推薦阿里云推廣服務(wù)器89/年,229/3年,買來送自己,送女朋友馬上過年再合適不過了,買了搭建個項目給面試官看也香,還可以熟悉技術(shù)棧,(老用戶用家人賬號買就好了,我用我女朋友的?)。掃碼購買



          我這里還有購買后的教程:搭建教程,從0開始一步一步帶你搭建?644f514e100575164f158169a9796e3a.webp



          80c3844f5fe143d144db30b50dbe7fe8.webp兩年嘔心瀝血的文章「面試題」「基礎(chǔ)」「進(jìn)階」這里全都有!

          瀏覽 42
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  美女被艹视频网站 | 91操人| 中国女人一级视频 | 爱爱短视频电影无码免费 | 成人无码免费看 |