JavaScript的遞迴與循環範例介紹_javascript技巧
對於不同類型的需要重複計算的問題,循環和遞歸兩種方法各有所長,能給出更直觀簡單的方案。另一方面,循環和遞歸的方法可以互相轉換。任何一個循環的程式碼都可以用遞歸改寫,實現相同的功能;反之亦然。在不失去其普遍性的前提下,可以把循環和遞歸分別用下列偽代碼概括。
偽代碼格式說明:循環採用while形式;變數不加定義;賦值用:=;條件表達式和執行的語句都寫成函數的形式,圓括號內寫上相關的值。其他語法方面,盡量接近Javascript的規格。
//pseudo code of a loop function loop(arguments){
//結果的初始值
result:=initial_value;
while(condition(variable, arguments)){//循環條件,可能只需arguments,也可能為了方便引入循環變數
//計算結果。參數包括先前的結果、目前循環變數和外部變數
result:=calculate(result, variable, extern_variables);
//影響函數的外部環境,即修改外部變數
changeStatus(result, variable , extern_variables);
//執行完循環體中的語句後,修改參數或循環變數。
modify_arguments_variable(arguments, variable);
}
//回傳結果
return result;
}
//以下程式碼為控制函數重複呼叫的結構部分。
//獲得再次呼叫此函數的新的參數,可能為多組arguments值。
//對應於循環中的condition(variable, arguments)和modify_arguments_variable(arguments, variable)。
new_arguments:=conditional_get_next(arguments);
//對新參數的每一組,呼叫函數本身。
results:=recursion(new_arguments);
//以下的程式碼為每次呼叫都執行的功能部分
//計算結果。涉及到先前的結果、當前循環變數和外部變數。
//對應於循環中的result:=calculate(result, variable, extern_variables)。
result:=calculate(arguments, extern_variables);
result:=combine(result, results);
//影響函數的外部環境,即修改外部變數
changeStatus(result, arguments, extern_variables);
return result;
}
籍比較兩段代碼,可以看出循環和遞歸具有相似的構成,透過改變順序和適當的變換,任何循環都可以用遞歸的方式實現。程序簡單時,這種轉換很容易看出。例如下面這個簡單的累計求和函數:
var result=1;
while (num>1){
result =num;
num--;
}
return result;
}
對應的遞歸形式:
if (num>1){
return num sum(num-1);
}else {
return 1;
}
}
反之,大部分遞歸程式也可以直接由迴圈來實現。下面是求最大公約數的循環形式的函數。
if (atemp=a;
a=b;
b=temp;
}
var c=a%b;
while (c!==0){
a=b;
b=c;
c=a%b;
}
return b;
}
不過,從遞歸到循環的轉換並不是都這麼容易。遞歸偽程式碼中的產生再次呼叫此函數的新參數部分
new_arguments:=conditional_get_next(arguments);
較之循環的對應部分更為靈活。可以依照新產生的參數組數(函數所需的所有參數為一組)將遞歸分為兩類。第一類為參數組數固定,此遞歸可以轉換為循環,例如斐波那契數列和最大公約數的例子;第二類為參數組數不確定——就像在遍歷一個圖或樹的時候那樣,每個點都有任意個相鄰的點-該遞迴不能直接轉換為迴圈。
因為循環只能做一維的重複,而遞歸可以遍歷二維的結構。例如一棵樹中,一個節點既有它的子節點,也有同級的節點,簡單的一維循環不能夠在兩個方向上遍歷。
但是如果我們在循環中藉助某種資料結構記住有關節點位置的一些信息,第二類遞歸也可以用循環實現。
我們再透過一個例子來實踐上面觀察得出的結論。 HTML5為Document和Element新定義了一個方法getElementsByClassName(names),傳回具有給定class值的所有elements。包括Firefox3在內的一些瀏覽器已經支援此方法。下面我們先用遞歸的方法給一個功能較弱的版本,然後再用循環的方法重寫它。
var getElementsByClass={}; //elem為一個HTMLElement
//name為單一class名稱
//傳回包含elem下所有class屬性包含給定名稱的element的陣列
getElementsByClass.recursion1=function (elem, name){
var list=[];
function getElements(el){
if (el.className.split(' ').indexOf(name)>-1){
list.push(el );
}
for (var i=0, c=el.children; i
}
}
getElements(elem);
return list;
}
如前所述,在循環中為了記住節點的位置信息,我們需要一個能實現以下方法的資料結構。
push(object) //寫入一個物件。
objectpop() //讀出最近寫入的一個對象,並將其從資料結構中刪除。
objectget() //讀出最近寫入的一個對象,不改變資料結構的內容。
堆疊正是這樣一個後進先出的資料結構。 Javascript中的Array物件支援前兩種方法,我們在為其增加第三個方法即可。
採用循環的版本:
stack.get = function(){
return stack[stack.length - 1];
}
var list = [];
//the business logic part. put the eligible element to the list.
function testElem(el ){
if (el.className.split(' ').indexOf(name) > -1) {
list.push(el);
}
}
//check the root element
testElem(elem);
//initialize the stack
stack.push({
pointer: elem,
num: 0
});
var parent, num, el;
while (true) {
parent = stack.get();
el = parent.pointer.children[parent.num];
if (el) {/ /enter a deeper layer of the tree
testElem(el);
stack.push({
pointer: el,
num: 0
});
}
num: 0
});
}
else {//return to the upper layer
if (stack.pop().pointer === elem) {
break;
}
else {
stack.get(). num = 1;
}
}
}
return list;
}
歸納起來。所有循環都可以用遞歸實現;所有遞歸都可以用循環實現。採用哪一種方法,由具體問題下哪一種思路較方便直覺和使用者的喜好決定。
效率
效能方面,遞歸不比循環有優勢。除了多次函數呼叫的開銷,在某些情況下,遞迴還會帶來不必要的重複計算。以計算斐波那契數列的遞歸程式為例。求第n項A(n)時,從第n-2項起,每一項都重複計算。項數越小,重複的次數越多。令B(i)為第i項被計算的次數,則有
B(i)=1; i=n, n-1
B(i)=B(i 1) B(i 2); i
這樣,B(i)形成了一個有趣的逆的斐波那契數列。求A(n)時有:
B(i)=A(n 1-i)
換一個角度來看,令C(i)為求A(i)時需要的加法的次數,則有
C(i)=0; i=0, 1
C(i)=1 C(i-1) C(i-1) ; i>1
令D(i)=C(i) 1,有
D(i)=1; i=0, 1
D(i )=D(i-1) D(i-1)
所以D(i)又形成一個斐波那契數列。並可因此得出:
C(n)=A(n 1)-1
而A(n)是以幾何級數增長,這種多餘的重複在n較大時會變得十分驚人。與之相對應的採用循環的程序,有
B(n)=1; n為任意值
C(n)=0; n=0, 1
C(n)=n-1; n>1
因而當n較大時,前面給出的採用循環的程序會比採用遞歸的程序快很多。
如上一節的循環一樣,遞歸中的這個缺陷也是可以彌補的。我們只需要記住已經計算出來的項,求較高項時,就可以直接讀取先前的項。這種技術在遞歸中很普遍,被稱為「儲存」(memorization)。
以下是採用儲存技術的求斐波那契數列的遞歸演算法。
//recursion with memorization
var memory = []; //used to store each calculated item
function calc(n){
var result, p, q;
if (n memory[n] = n;
return n;
}
else {
p = memory[n - 1] ? memory[n - 1] : calc(n - 1);
q = memory[n - 2] ? memory[n - 2] : calc(n - 2);
result = p q;
memory[n] = result;
return result;
}
}
return calc(n);
}

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

C++函數的遞歸深度受到限制,超過此限制會導致堆疊溢位錯誤。限制值因係統和編譯器而異,通常在1000到10000之間。解決方法包括:1.尾遞歸最佳化;2.尾呼叫;3.迭代實作。

是的,C++Lambda表達式可以透過使用std::function支援遞歸:使用std::function捕捉Lambda表達式的參考。透過捕獲的引用,Lambda表達式可以遞歸呼叫自身。

給定兩個字串str_1和str_2。目標是使用遞歸過程計算字串str1中子字串str2的出現次數。遞歸函數是在其定義中呼叫自身的函數。如果str1是"Iknowthatyouknowthatiknow",str2是"know"出現次數為-3讓我們透過範例來理解。例如輸入str1="TPisTPareTPamTP",str2="TP";輸出Countofoccurrencesofasubstringrecursi

遞歸演算法透過函數自呼叫解決結構化的問題,優點是簡潔易懂,缺點是效率較低且可能發生堆疊溢位;非遞歸演算法透過明確管理堆疊資料結構避免遞歸,優點是效率更高且避免堆疊溢出,缺點是程式碼可能更複雜。選擇遞歸或非遞歸取決於問題和實現的特定限制。

尾遞歸最佳化(TRO)可提高特定遞歸呼叫的效率。它將尾遞歸呼叫轉換為跳轉指令,並將上下文狀態保存在暫存器中,而不是堆疊上,從而消除對堆疊的額外呼叫和返回操作,提高演算法效率。利用TRO,我們可以針對尾遞歸函數(例如階乘計算)進行最佳化,透過將tail遞歸呼叫替換為goto語句,編譯器會將goto跳轉移化為TRO,最佳化遞歸演算法的執行。

遞歸函數是一種在字串處理中反覆呼叫自身來解決問題的技術。它需要一個終止條件以防止無限遞歸。遞歸在字串反轉和回文檢查等操作中被廣泛使用。

遞歸定義及最佳化:遞歸:函數內部呼叫自身,解決可分解為更小子問題的難題。尾遞歸:函數進行所有計算後才進行遞歸調用,可最佳化為循環。尾遞歸最佳化條件:遞歸呼叫為最後操作。遞歸呼叫參數與原始呼叫參數相同。實戰範例:計算階乘:輔助函數factorial_helper實現尾遞歸最佳化,消除呼叫棧,提高效率。計算斐波那契數列:尾遞歸函數fibonacci_helper利用最佳化,高效率計算斐波那契數。

lambda表達式跳出循環,需要具體程式碼範例在程式設計中,循環結構是常用的一種重要語法。然而,在特定的情況下,我們可能希望在循環體內滿足某個條件時,跳出整個循環,而不是僅僅終止當前的循環迭代。在這個時候,lambda表達式的特性可以幫助我們達成跳脫循環的目標。 lambda表達式是一種匿名函數的宣告方式,它可以在內部定義簡單的函數邏輯。它與普通的函數聲明不同,
