999精品在线视频,手机成人午夜在线视频,久久不卡国产精品无码,中日无码在线观看,成人av手机在线观看,日韩精品亚洲一区中文字幕,亚洲av无码人妻,四虎国产在线观看 ?

詳解“尾遞歸”

2020-09-29 07:51:13方悅
電腦知識與技術 2020年17期
關鍵詞:優化

摘要:該文首先講解了“尾調用”的概念和性質,介紹了“尾調用優化”的實用意義。“尾調用”與“遞歸”的結合,引出了“尾遞歸”。隨后以JS環境下的斐波那契數列(Fibonacci)為例探討了其使用方法與改造技巧,最后簡單講述了尾遞歸優化的實現原理。

關鍵詞:尾調用;優化;遞歸;尾遞歸;柯里化

中圖分類號:TP311 ? ? ?文獻標識碼:A

文章編號:1009-3044(2020)17-0208-03

Abstract: This paper introduces the definition and character of tail call, as well as the utility of Tail Call Optimization(TCO). The combination of tail call and recursion generates “tail recursion”. Next, I discuss the method of use and transformation skill through the example of Fibonacci in the JS programming environment. Last, the implementation principle of TCO is explained briefly.

Key words: tail call; optimization; recursion; tail recursion; currying

尾調用(Tail Call)是函數式編程的一個重要概念。當一個函數處于(另)一個函數的尾部,準確地說,是在最后一條返回語句(return)中的時候,我們稱之為尾調用。從主調函數的角度看,執行的最后一個動作是返回(另)一個函數的運行結果;從被調函數的角度看,運行結果直接被另一個函數返回了。通常情況下,一個函數的返回值是一個簡單的數值;而在尾調用中,如果不考慮遞歸的話,返回的是另一個函數的運行結果。如:

值得注意的是,由于程序在返回時可能存在分支結構,因此尾調用不一定是字面上的最后一條語句;這里的“尾”字,指的是程序執行的最后一個動作,我們稱該調用位置是函數的尾位置。舉個簡單的例子:

不難看出,這里的尾位置是對a()的調用和返回就不是函數x書面上的最后一條語句。如果情況稍復雜些,則:

這里的函數y和z都不是尾調用,即使z函數在語義上沒有變化。當然,如果一個函數沒有return,也就是不返回數值的話,就更談不上尾調用了。

在程序運行時,計算機會為應用程序分配一定的內存空間;應用程序則會自行分配所獲得的內存空間,其中一部分被用于記錄程序中正在調用的各個函數的運行情況,這就是函數的調用棧(call stack)。一般的函數調用總是會在調用棧最上層添加一個新的棧幀(stack frame),這個過程被稱作“入棧”或“壓棧”(push)。當函數的調用層數非常多時,調用棧會消耗掉不少內存,甚至會撐爆棧空間造成溢出,導致程序嚴重卡頓或意外崩潰。

在傳統的程序調用的過程中,計算機必須“記住”調用函數的返回位置,才能在調用結束后返回該位置繼續執行后續命令,該位置信息(即下一條指令的內存地址)一般被存放在調用棧上。與之不同的是,在尾調用這種特殊的情形中,由于調用下級函數以后上級函數就結束了,所以執行到最后一步,計算機可以不需要記住尾調用的返回位置,而是帶著返回值直接從被調函數越級跳轉到調用函數的返回位置,相當于連續返回了兩次,減少了棧中一次調用幀的存取,如圖1所示。

由于尾調用是外層函數的最后一步操作,尾調用返回后,外層函數也就返回了。執行尾調用的時候,外層函數棧幀中保存的調用位置、內部變量等信息都不會再用到了,所以,可以用內層函數(即尾調用函數)的棧幀覆蓋外層函數的棧幀(而不是在外層函數棧幀下面再新開一個),這樣可以節省棧空間。

如果覺得有些抽象,可以舉個具體例子:

很明顯的,調用g(3)之后,函數f()就結束了,所以當執行到g(3)的時候,完全可以用g(3)的棧幀覆蓋f()的棧幀。

從原理上我們發現,尾調用的棧是易于優化的。無論有多少層尾調用,都可以通過消除保持棧中的調用幀始終只有一條,從而減少內存使用,提高運行速度。在圖2中,func0和func1的局部變量、func1和func2的返回位置等數據都變為了無用,于是可依次用func1和func2的棧幀迭代覆蓋掉func0的棧幀,而不是在func0、func1或func2棧幀外再新開一個。尾調用優化(Tail Call Optimization, TCO)或尾調用消除(Tail Call Elimination)讓尾位置的函數返回跟goto語句的執行效率一樣高,在匯編層面成功地用jmp替代了call的職能。理論上講尾調用可以通過簡化函數調用棧的結構獲得性能提升,但實際上尾調用消除是否能真正起作用決定于運行環境是否支持此類優化,以及編程時是否開啟這兩方面的因素。

尾調用優化是從ES6才開始出現的概念,以JavaScript為例,尾調用優化只有在嚴格模式(strict mode)下才有效,在正常模式下是無效的。該模式是ECMA-262規范定義的JavaScript標準,其開啟命令"use strict"在 JavaScript 1.8.5 (ECMA Script5) 中增加,發布于2009年12月。表達式"use strict"的聲明必須在腳本或函數的開頭添加。它不是一條語句,而是一個字符串字面量,在 JavaScript 舊版本中會被忽略。目前主流瀏覽器都已支持嚴格模式,如:Internet Explorer 10 +、Firefox 4+、Chrome 13+、Safari 5.1+、Opera 12+、IOS 5+、Android 3+等①。在嚴格模式下,存在諸如:不允許使用未聲明的變量,不允許刪除變量或對象,不允許刪除函數等限制,需要注意。

如果是用GCC編譯的話,加上-O2或-O3的優化參數就可以輕松識別尾調用了;VC也有類似選項。不過值得指出的是,在C++、C#等語言的函數體中若存在結構體或類的構造,那么在調用結束返回時,由編譯器自動生成的析構函數很可能會取代return語句的末尾位置,隱式的變為調用函數的“最后一個動作”,依據定義判斷這就不是尾調用了。為了解決這一問題,C++引入了一項編譯優化技術,叫作“返回值優化”(Return Value Optimization, RVO)。基本手段是將待返回的對象構造在調用者的棧幀上,這樣調用者就可以直接訪問這個對象而不必復制了,自然也省去了調用析構函數。可以認為是強制開啟了尾調用優化。

一般來說,尾調用消除是可選的,可以用,也可以不用。但是,在函數式編程語言中,語言標準通常會要求編譯器或運行平臺實現尾調用消除,讓程序員可以用遞歸代替循環而不喪失性能。尾遞歸的優化效果最為明顯,尤其是遞歸算法非常復雜的情形。

在尾調用中有一種特殊但重要的情況叫作尾遞歸。一般地,函數調用自身,稱為遞歸。如果是尾調用自身,就稱為尾遞歸。我們知道,遞歸作為一種解決復雜問題的方法思路,有時候比迭代更具有可讀性和可維護性,但不得不考慮棧資源的耗費;由于需要同時保存所有中間函數的調用幀,容易因壓棧過深帶來性能下降,更面臨著“棧溢出”(stack overflow)錯誤的風險。尾遞歸很好地解決了這一矛盾——由于只存在一個調用幀,所以永遠不會發生“棧溢出”的錯誤。以累加求和為例,f(n) = f(n-1) + n; 會保存n個調用幀,而使用尾遞歸f(n, sum) = f(n-1, sum+n); 則只需保留最后一個棧幀即可,前面的都可以刪掉,這樣復雜度也從O(n)降到了O(1)。經過適當處理,尾遞歸形式的函數的運行效率可以得到極大優化,達到與循環相當的水平。以斐波那契數列(Fibonacci)為例:

我們發現,尾遞歸的實現,往往需要改寫遞歸函數,確保最后一步只調用自身。要做到這一點,就須把所有用到的內部變量改寫成函數的參數。這樣做看起來不大直觀,解決辦法有兩種:

方法1:在尾遞歸函數之外,再提供一個正常形式的函數。

方法2:函數式編程里有個術語叫作currying,音譯為“柯里化”,意思是將接收多參數的函數表面上轉換成只接收單參數函數的形式,實際上是返回一個用來接收余下參數的新函數的技術。聽起來有點繞,下面看看具體實例。

curry,其實就是我們所熟知的“咖喱”;currying,意為烹制咖喱菜。特點是把許多種香料(如:姜黃,胡荽籽,辣椒,孜然,小茴香,白胡椒,花椒,芥末等)分別準備好后,放在一起煮,合成一道菜,有點像“八寶粥”的意思。用其命名,十分形象。在上例中Fibonacci傳遞n,currying調用tailFibonacci、傳遞 1, 1,最后合在一起。“柯里化”在本質上就是一個分步處理參數的過程,可以使函數更具可讀性和彈性。見下:

前面我們已經知道了如何將非尾遞歸函數改寫為尾遞歸的形式,以便使用尾調用優化。進一步的,我們來了解一下這樣的尾遞歸優化在計算機系統中是如何實現的,這有利于我們在一些無法使用尾調用優化的情況下(如:ES5環境或程序不符合尾調用范式)手動產生那樣的效果。

下述tail函數的精妙之處在于狀態變量active的運用。默認情況下,該變量是不激活的;而一旦進入尾遞歸優化的過程,這個變量就激活了。在上例的第一次調用后,變量active會被“激活”。于是后續每次遞歸,都只是將本次接收的參數推入stack數組,直接返回不進入下一輪遞歸;而返回后由于stack數組里有一個數組項,通過while循環又處理新的參數列表,所以就會一直重復“進入遞歸->獲得參數列表->返回->進入遞歸->...”這樣的輪回,直到某次遞歸沒有向stack數組推入新的參數為止。如此一來就很巧妙地將“遞歸”改寫成了“循環”,后一輪的參數會取代前一輪的參數,保證調用棧永遠只有一層。

補充一點說明,雖然在此我們多用JS舉例,但對于C++等編程語言也完全適用。

綜上所述我們看到,尾遞歸由于可通過“尾調用消除”技術予以簡化,從而具有不同于一般遞歸的重要性。本文系統闡述了尾遞歸的優化原理、使用方法和構造技巧,熟練掌握并靈活運用將會給編程愛好者帶來不小的啟發和幫助。

注釋:

① 引自網站”Can I use... Support tables for HTML5, CSS3, etc,https://caniuse.com/#feat=use-strict.

參考文獻:

[1] 方悅. 循環、迭代與遞歸[J]. 電腦知識與技術, 2020, 16(6): 55-57, 66.

[2] 阮一峰. ECMAScript 6 入門[M]. 北京: 電子工業出版社, 2014.

[3] 崔蕊. 遞歸到非遞歸算法的轉換[J]. 電腦知識與技術, 2009, 5(23): 6424-6425, 6458.

[4] 張國. 基于遞歸算法的非遞歸實現研究[J]. 長江大學學報(自然科學版)理工卷, 2009, 6(3): 223-225.

[5] 高漢平, 方志雄. 從遞歸算法到非遞歸的變換[J]. 黃岡師范學院學報, 2002, 22(3): 47-50.

【通聯編輯:謝媛媛】

猜你喜歡
優化
超限高層建筑結構設計與優化思考
房地產導刊(2022年5期)2022-06-01 06:20:14
PEMFC流道的多目標優化
能源工程(2022年1期)2022-03-29 01:06:28
民用建筑防煙排煙設計優化探討
關于優化消防安全告知承諾的一些思考
一道優化題的幾何解法
由“形”啟“數”優化運算——以2021年解析幾何高考題為例
圍繞“地、業、人”優化產業扶貧
今日農業(2020年16期)2020-12-14 15:04:59
事業單位中固定資產會計處理的優化
消費導刊(2018年8期)2018-05-25 13:20:08
4K HDR性能大幅度優化 JVC DLA-X8 18 BC
幾種常見的負載均衡算法的優化
電子制作(2017年20期)2017-04-26 06:57:45
主站蜘蛛池模板: 欧美精品亚洲日韩a| 波多野结衣一区二区三区AV| 麻豆精品在线播放| 熟妇无码人妻| 欧美区一区| 国产在线精品99一区不卡| 欧美午夜视频| 这里只有精品在线| 无码在线激情片| 午夜视频日本| 久久精品丝袜| 色首页AV在线| 国产成人久久777777| 无码日韩精品91超碰| 国产精彩视频在线观看| …亚洲 欧洲 另类 春色| 国产成人资源| 97se亚洲综合在线天天| 8090午夜无码专区| 国产精品中文免费福利| 国外欧美一区另类中文字幕| 国产农村1级毛片| 波多野结衣二区| 天天躁日日躁狠狠躁中文字幕| 国产激情在线视频| 极品国产一区二区三区| 国产成人精品午夜视频'| 欧美日韩一区二区三区在线视频| 伊人激情久久综合中文字幕| 高清国产va日韩亚洲免费午夜电影| 亚洲国产91人成在线| 国产精品成人一区二区| 伦伦影院精品一区| 最新国产麻豆aⅴ精品无| 91青青视频| 国产无码高清视频不卡| 久久这里只有精品2| 中文无码日韩精品| 国产av一码二码三码无码| a天堂视频在线| 欧美精品v| 91福利免费视频| 激情无码字幕综合| 亚洲国产成人在线| 毛片网站在线播放| 幺女国产一级毛片| 97在线视频免费观看| 潮喷在线无码白浆| 亚洲天堂视频在线观看免费| 免费在线a视频| 四虎永久在线视频| 激情综合五月网| 久久男人资源站| 成人字幕网视频在线观看| 动漫精品中文字幕无码| 成人免费午间影院在线观看| 99国产精品免费观看视频| 亚洲精品在线影院| 午夜a级毛片| 波多野结衣视频网站| 91精品啪在线观看国产60岁| 国产精品久久久久久搜索| 日韩黄色在线| 亚洲av无码久久无遮挡| 无码一区18禁| 亚洲欧美综合另类图片小说区| 久久久亚洲色| 国产成人高清精品免费5388| 99在线国产| 亚洲国产综合自在线另类| 不卡色老大久久综合网| 无码日韩人妻精品久久蜜桃| 成年人福利视频| 免费A∨中文乱码专区| 青青热久麻豆精品视频在线观看| 色悠久久综合| 亚洲人人视频| 国产精品吹潮在线观看中文| 久久九九热视频| 亚洲成肉网| 欧美色综合网站| 人妻无码一区二区视频|