如何利用迴圈代替遞迴以防止棧溢位(譯)
摘要:我們經常會用到遞迴函式,但是如果遞迴深度太大時,往往導致棧溢位。而遞迴深度往往不太容易把握,所以比較安全一點的做法就是:用迴圈代替遞迴。文章最後的原文裡面講了如何用10步實現這個過程,相當精彩。本文翻譯了這篇文章,並加了自己的一點註釋和理解。
目錄
- 簡介
- 模擬函式的目的
- 遞迴和模擬函式的優缺點
- 用棧和迴圈代替遞迴的10個步驟
- 替代過程的幾個簡單例子
- 更多的例子
- 結論
- 參考
- 協議
1 簡介
一般我們在進行排序(比如歸併排序)或者樹操作時會用到遞迴函式。但是如果遞迴深度達到一定程度以後,就會出現意想不到的結果比如堆疊溢位。雖然有很多有經驗的開發者都知道了如何用迴圈函式
2 模擬函式的目的
如果你正在使用遞迴函式,並且沒有控制遞迴呼叫,而棧資源又比較有限,呼叫層次過深的時候就可能導致棧溢位/堆衝突。模擬函式的目的就是在堆中開闢區域來模擬棧的行為,這樣你就能控制記憶體分配和流處理,從而避免棧溢位。如果能用迴圈函式來代替效果會更好,這是一個比較需要時間和經驗來處理的事情,出於這些原因,這篇文章為初學者提供了一個簡單的參考,怎樣使用迴圈函式來替代遞迴函式,以防止棧溢位?
3 遞迴函式和模擬函式的優缺點
遞迴函式:
優點:演算法比較直觀。可以參考文章後面的例子
缺點:可能導致棧溢位或者堆衝突
你可以試試執行下面兩個函式(後面的一個例子),IsEvenNumber(遞迴實現)和IsEvenNumber(模擬實現),他們在標頭檔案"MutualRecursion.h"中宣告。你可以將傳入引數設定為10000,像下面這樣:
#include "MutualRecursion.h" bool result = IsEvenNumberLoop(10000); // 成功返回 bool result2 = IsEvenNumber(10000); // 會發生堆疊溢位
有些人可能會問:如果我增加棧的容量不就可以避免棧溢位嗎?好吧,這只是暫時的解決問題的辦法,如果呼叫層次越來越深,很有可能會再次發生溢位。
模擬函式:
優點:能避免棧溢位或者堆衝突錯誤,能對過程和記憶體進行更好的控制
缺點:演算法不是太直觀,程式碼難以維護
4 用棧和迴圈代替遞迴的10個步驟
第一步
1 定義一個新的結構體Snapshot,用於儲存遞迴結構中的一些資料和狀態資訊
2 在Snapshot內部需要包含的變數有以下幾種:
A 一般當遞迴函式呼叫自身時,函式引數會發生變化。所以你需要包含變化的引數,引用除外。比如下面的例子中,引數n應該包含在結構體中,而retVal不需要。
void SomeFunc(int n, int &retVal);
B 階段性變數"stage"(通常是一個用來轉換到另一個處理分支的整形變數),詳見第六條規則
C 函式呼叫返回以後還需要繼續使用的區域性變數(一般在二分遞迴和巢狀遞迴中很常見)
程式碼:
1 // Recursive Function "First rule" example 2 int SomeFunc(int n, int &retIdx) 3 { 4 ... 5 if(n>0) 6 { 7 int test = SomeFunc(n-1, retIdx); 8 test--; 9 ... 10 return test; 11 } 12 ... 13 return 0; 14 } 15 16 17 // Conversion to Iterative Function 18 int SomeFuncLoop(int n, int &retIdx) 19 { 20 // (First rule) 21 struct SnapShotStruct { 22 int n; // - parameter input 23 int test; // - local variable that will be used 24 // after returning from the function call 25 // - retIdx can be ignored since it is a reference. 26 int stage; // - Since there is process needed to be done 27 // after recursive call. (Sixth rule) 28 }; 29 ... 30 }View Code
第二步
1 在函式的開頭建立一個區域性變數,這個值扮演了遞迴函式的返回函式角色。它相當於為每次遞迴呼叫儲存一個臨時值,因為C++函式只能有一種返回型別,如果遞迴函式的返回型別是void,你可以忽略這個區域性變數。如果有預設的返回值,就應該用預設值初始化這個區域性變數。
1 // Recursive Function "Second rule" example 2 int SomeFunc(int n, int &retIdx) 3 { 4 ... 5 if(n>0) 6 { 7 int test = SomeFunc(n-1, retIdx); 8 test--; 9 ... 10 return test; 11 } 12 ... 13 return 0; 14 } 15 16 // Conversion to Iterative Function 17 int SomeFuncLoop(int n, int &retIdx) 18 { 19 // (First rule) 20 struct SnapShotStruct { 21 int n; // - parameter input 22 int test; // - local variable that will be used 23 // after returning from the function call 24 // - retIdx can be ignored since it is a reference. 25 int stage; // - Since there is process needed to be done 26 // after recursive call. (Sixth rule) 27 }; 28 29 // (Second rule) 30 int retVal = 0; // initialize with default returning value 31 32 ... 33 // (Second rule) 34 return retVal; 35 }View Code
第三步
建立一個棧用於儲存“Snapshot”結構體型別變數
1 // Recursive Function "Third rule" example 2 3 // Conversion to Iterative Function 4 int SomeFuncLoop(int n, int &retIdx) 5 { 6 // (First rule) 7 struct SnapShotStruct { 8 int n; // - parameter input 9 int test; // - local variable that will be used 10 // after returning from the function call 11 // - retIdx can be ignored since it is a reference. 12 int stage; // - Since there is process needed to be done 13 // after recursive call. (Sixth rule) 14 }; 15 16 // (Second rule) 17 int retVal = 0; // initialize with default returning value 18 19 // (Third rule) 20 stack<SnapShotStruct> snapshotStack; 21 ... 22 // (Second rule) 23 return retVal; 24 }View Code
第四步
建立一個新的”Snapshot”例項,然後將其中的引數等初始化,並將“Snapshot”例項壓入棧
1 // Recursive Function "Fourth rule" example 2 3 // Conversion to Iterative Function 4 int SomeFuncLoop(int n, int &retIdx) 5 { 6 // (First rule) 7 struct SnapShotStruct { 8 int n; // - parameter input 9 int test; // - local variable that will be used 10 // after returning from the function call 11 // - retIdx can be ignored since it is a reference. 12 int stage; // - Since there is process needed to be done 13 // after recursive call. (Sixth rule) 14 }; 15 16 // (Second rule) 17 int retVal = 0; // initialize with default returning value 18 19 // (Third rule) 20 stack<SnapShotStruct> snapshotStack; 21 22 // (Fourth rule) 23 SnapShotStruct currentSnapshot; 24 currentSnapshot.n= n; // set the value as parameter value 25 currentSnapshot.test=0; // set the value as default value 26 currentSnapshot.stage=0; // set the value as initial stage 27 28 snapshotStack.push(currentSnapshot); 29 30 ... 31 // (Second rule) 32 return retVal; 33 }View Code
第五步
寫一個while迴圈,使其不斷執行直到棧為空。在while迴圈的每一次迭代過程中,彈出”Snapshot“物件。
1 // Recursive Function "Fifth rule" example 2 3 // Conversion to Iterative Function 4 int SomeFuncLoop(int n, int &retIdx) 5 { 6 // (First rule) 7 struct SnapShotStruct { 8 int n; // - parameter input 9 int test; // - local variable that will be used 10 // after returning from the function call 11 // - retIdx can be ignored since it is a reference. 12 int stage; // - Since there is process needed to be done 13 // after recursive call. (Sixth rule) 14 }; 15 // (Second rule) 16 int retVal = 0; // initialize with default returning value 17 // (Third rule) 18 stack<SnapShotStruct> snapshotStack; 19 // (Fourth rule) 20 SnapShotStruct currentSnapshot; 21 currentSnapshot.n= n; // set the value as parameter value 22 currentSnapshot.test=0; // set the value as default value 23 currentSnapshot.stage=0; // set the value as initial stage 24 snapshotStack.push(currentSnapshot); 25 // (Fifth rule) 26 while(!snapshotStack.empty()) 27 { 28 currentSnapshot=snapshotStack.top(); 29 snapshotStack.pop(); 30 ... 31 } 32 // (Second rule) 33 return retVal; 34 }View Code
第六步
- 將當前階段一分為二(針對當前只有單一遞迴呼叫的情形)。第一個階段代表了下一次遞迴呼叫之前的情況,第二階段代表了下一次遞迴呼叫完成並返回之後的情況(返回值已經被儲存,並在此之前被累加)。
- 如果當前階段有兩次遞迴呼叫,就必須分為3個階段。階段1:第一次呼叫返回之前,階段2:階段1執行的呼叫過程。階段3:第二次呼叫返回之前。
- 如果當前階段有三次遞迴呼叫,就必須至少分為4個階段。
- 依次類推。
1 // Recursive Function "Sixth rule" example 2 int SomeFunc(int n, int &retIdx) 3 { 4 ... 5 if(n>0) 6 { 7 int test = SomeFunc(n-1, retIdx); 8 test--; 9 ... 10 return test; 11 } 12 ... 13 return 0; 14 } 15 16 // Conversion to Iterative Function 17 int SomeFuncLoop(int n, int &retIdx) 18 { 19 // (First rule) 20 struct SnapShotStruct { 21 int n; // - parameter input 22 int test; // - local variable that will be used 23 // after returning from the function call 24 // - retIdx can be ignored since it is a reference. 25 int stage; // - Since there is process needed to be done 26 // after recursive call. (Sixth rule) 27 }; 28 // (Second rule) 29 int retVal = 0; // initialize with default returning value 30 // (Third rule) 31 stack<SnapShotStruct> snapshotStack; 32 // (Fourth rule) 33 SnapShotStruct currentSnapshot; 34 currentSnapshot.n= n; // set the value as parameter value 35 currentSnapshot.test=0; // set the value as default value 36 currentSnapshot.stage=0; // set the value as initial stage 37 snapshotStack.push(currentSnapshot); 38 // (Fifth rule) 39 while(!snapshotStack.empty()) 40 { 41 currentSnapshot=snapshotStack.top(); 42 snapshotStack.pop(); 43 // (Sixth rule) 44 switch( currentSnapshot.stage) 45 { 46 case 0: 47 ... // before ( SomeFunc(n-1, retIdx); ) 48 break; 49 case 1: 50 ... // after ( SomeFunc(n-1, retIdx); ) 51 break; 52 } 53 } 54 // (Second rule) 55 return retVal; 56 }View Code
第七步
根據階段變數stage的值切換到相應的處理流程並處理相關過程。
1 // Recursive Function "Seventh rule" example 2 int SomeFunc(int n, int &retIdx) 3 { 4 ... 5 if(n>0) 6 { 7 int test = SomeFunc(n-1, retIdx); 8 test--; 9 ... 10 return test; 11 } 12 ... 13 return 0; 14 } 15 16 // Conversion to Iterative Function 17 int SomeFuncLoop(int n, int &retIdx) 18 { 19 // (First rule) 20 struct SnapShotStruct { 21 int n; // - parameter input 22 int test; // - local variable that will be used 23 // after returning from the function call 24 // - retIdx can be ignored since it is a reference. 25 int stage; // - Since there is process needed to be done 26 // after recursive call. (Sixth rule) 27 }; 28 29 // (Second rule) 30 int retVal = 0; // initialize with default returning value 31 32 // (Third rule) 33 stack<SnapShotStruct> snapshotStack; 34 35 // (Fourth rule) 36 SnapShotStruct currentSnapshot; 37 currentSnapshot.n= n; // set the value as parameter value 38 currentSnapshot.test=0; // set the value as default value 39 currentSnapshot.stage=0; // set the value as initial stage 40 41 snapshotStack.push(currentSnapshot); 42 43 // (Fifth rule) 44 while(!snapshotStack.empty()) 45 { 46 currentSnapshot=snapshotStack.top(); 47 snapshotStack.pop(); 48 49 // (Sixth rule) 50 switch( currentSnapshot.stage) 51 { 52 case 0: 53 // (Seventh rule) 54 if( currentSnapshot.n>0 ) 55 { 56 ... 57 } 58 ... 59 break; 60 case 1: 61 // (Seventh rule) 62 currentSnapshot.test = retVal; 63 currentSnapshot.test--; 64 ... 65 break; 66 } 67 } 68 // (Second rule) 69 return retVal; 70 }View Code
第八步
如果遞迴有返回值,將這個值儲存下來放在臨時變數裡面,比如retVal。當迴圈結束時,這個臨時變數的值就是整個遞迴處理的結果。
1 // Recursive Function "Eighth rule" example 2 int SomeFunc(int n, int &retIdx) 3 { 4 ... 5 if(n>0) 6 { 7 int test = SomeFunc(n-1, retIdx); 8 test--; 9 ... 10 return test; 11 } 12 ... 13 return 0; 14 } 15 16 // Conversion to Iterative Function 17 int SomeFuncLoop(int n, int &retIdx) 18 { 19 // (First rule) 20 struct SnapShotStruct { 21 int n; // - parameter input 22 int test; // - local variable that will be used 23 // after returning from the function call 24 // - retIdx can be ignored since it is a reference. 25 int stage; // - Since there is process needed to be done 26 // after recursive call. (Sixth rule) 27 }; 28 // (Second rule) 29 int retVal = 0; // initialize with default returning value 30 // (Third rule) 31 stack<SnapShotStruct> snapshotStack; 32 // (Fourth rule) 33 SnapShotStruct currentSnapshot; 34 currentSnapshot.n= n; // set the value as parameter value 35 currentSnapshot.test=0; // set the value as default value 36 currentSnapshot.stage=0; // set the value as initial stage 37 snapshotStack.push(currentSnapshot); 38 // (Fifth rule) 39 while(!snapshotStack.empty()) 40 { 41 currentSnapshot=snapshotStack.top(); 42 snapshotStack.pop(); 43 // (Sixth rule) 44 switch( currentSnapshot.stage) 45 { 46 case 0: 47 // (Seventh rule) 48 if( currentSnapshot.n>0 ) 49 { 50 ... 51 } 52 ... 53 // (Eighth rule) 54 retVal = 0 ; 55 ... 56 break; 57 case 1: 58 // (Seventh rule) 59 currentSnapshot.test = retVal; 60 currentSnapshot.test--; 61 ... 62 // (Eighth rule) 63 retVal = currentSnapshot.test; 64 ... 65 break; 66 } 67 } 68 // (Second rule) 69 return retVal; 70 }View Code
第九步
如果遞迴函式有“
摘要:我們經常會用到遞迴函式,但是如果遞迴深度太大時,往往導致棧溢位。而遞迴深度往往不太容易把握,所以比較安全一點的做法就是:用迴圈代替遞迴。文章最後的原文裡面講了如何用10步實現這個過程,相當精彩。本文翻譯了這篇文章,並加了自己的一點註釋和理解。
目錄
簡介
那麼過多的遞迴呼叫為什麼會引起棧溢位呢?事實上,函式呼叫的引數是通過棧空間來傳遞的,在呼叫過程中會佔用執行緒的棧資源。而遞迴呼叫,只有走到最後的結束點後函式才能依次退出,而未到達最後的結束點之前,佔用的棧空間一直沒有釋放,如果遞迴呼叫次數過多,就可能導致佔用的棧資源超過執
有一類問題,可以歸結為如下形式:
已知兩個條件:1,f(n) 和 f(n-1) 的遞推關係式; 2,某個f(x) 的具體值。求解:f(n)。
跟高中的某一類數列問題一模一樣...
這個問題有兩條思路:
a,正向推導:根據 f(x) 可以求解 f(x+1)的值,再求f(x+2),...
一、一版的遞迴實現 n!,比如 5!= 5 * 4 * 3 * 2 *1
function fact(n) {
if(n == 1) {
一、迴圈
GIF 1:最簡單的 while 迴圈
GIF 2:帶 if/else 的迴圈
二、遞迴
GIF 3:遞迴概念的直接演示
GIF 4:遞迴的程式碼示例
GIF 5:遞迴求斐波那契數列
GIF 6:遞迴求階乘(圖裡縮排有點問題,請忽
1.二叉樹介紹
二叉樹是每個節點最多有兩個子樹的樹結構,遍歷方法有深度優先(包括:先序、中序、後序遍歷)和寬度優先(層序遍歷),層序遍歷通過佇列可以實現。這裡主要介紹深度優先遍歷的方法以及其中棧的應用,幫助理解二叉樹的結構、遞迴和非遞迴中棧的應用。程式pyth
import java.util.Stack;
class Solution {
private static void reverse(Stack<Integer> s) 我們知道遞迴演算法非常低效,低效的原因在於遞迴的過程會產生冗餘計算。
拿我們熟悉的斐波那契數列為例,計算公式為:F(n) = F(n - 1) + F(n - 2),其中F(0) = F(1) = 1。
例如計算F(5)的執行過程:
在此過程中,F(4) 執行了1次;F(3)執行了2次;F(2)執行
python中,迴圈與遞迴舉例,包括階乘、計算和等。
1、計算階乘:5!
1)迴圈方法計算
# 迴圈方法計算階乘:5!
def fact1(n):
i = 1
result = 1
while i <= n:
result = r
WITH AS短語,也叫做子查詢部分(subquery factoring),在SQL Server 2005中提供了一種解決方案,這就是公用表表達式(CTE),使用CTE,可以使SQL語句的可維護性,同時,CTE要比表變數的效率高得多。
下面是CTE的語法:
題目
一個棧依次壓入1,2,3, 4, 5,那麼從棧頂到棧底分別為5, 4, 3, 2, 1。將這個棧轉置後,從棧頂到棧底為1,2,3, 4, 5,也就是實現棧中元素的逆序,但是隻能使用遞迴函式來實現,不能使用其他資料結構。
解題參考和一些坑
共兩個遞迴函式來實現逆序: 第一個函
遞迴
1.前序遍歷
void preorder(BinTree *T)
{
if(T==NULL)
return;
cout << T->data;
preorder(T->left);
preorder(T->rig
筆記來自【晴神寶典】
一、遞迴
遞迴 就在於反覆呼叫自身函式,但是每次都把問題範圍縮小,直到範圍可以縮小到可以直接得到邊界資料的結果,然後在返回路上求出對應的解。以上可看出,遞迴很適合用來實現分治思想。
遞迴兩個很重要的組成組成:
1、遞迴邊界(出口);
2、遞迴式
遞迴函式由於在運算中,重複的遞迴呼叫自身,函式的區域性變數所佔用的記憶體空間持續增長並不會被釋放,導致區域性變數所佔用的棧記憶體可用空間越來越少,當遞迴呼叫深度達到一定量級,就可能會使得棧記憶體空間不足,導致記憶體分配失敗。所以當設計遞迴函式的時候要注意遞迴的深度所可能達到的上限,避免該
<?php
header("content-type:text/html;charset=utf-8");
/*
* 僅用遞迴函式和棧操作逆序一個棧 P8
*/
function getAndRemoveLastElement(SplStack $stack){
if($stack- /** * 如何僅用遞迴函式和棧操作逆序一個棧 * 題目: * 一個棧依次壓入1,2,3,4,5,那麼從棧頂到棧底分別為5,4,3,2,1。 * 將這個棧轉置後,從棧頂到棧底為1,2,3,4,5,也就是實現棧中元素的逆序, * 但是隻能用遞迴函式來實現,不能用 用mysql儲存過程代替遞迴查詢
查詢此表某個id=4028ab535e370cd7015e37835f52014b(公司1)下的所有資料
正常情況下,我們採用遞迴演算法查詢,如下
1
function getSum(n) { var n1 = 1; //初始化兩個月的兔子個數 var n2 = 1; var sum = 1; //定義一個累加和 ,如果傳遞的是1或者2,預設值為1 for(var i = 3; i <= n; i++) { sum =
* Stack.php
<?php
/**
* Created by PhpStorm.
* User: Mch
* Date: 9/24/18
* Time: 9:30 AM
*/
namespace ds\stack;
class Stack ext
返回棧中最小元素
package abc;
import java.util.Stack;
/**
* 實現一個特殊的棧,在實現基本功能的基礎上,返回棧中最小元素的操作
* pop push getMin時間複雜度都是O(1)
* 設計的棧型別可以 相關推薦
如何利用迴圈代替遞迴以防止棧溢位(譯)
遞迴呼叫中棧溢位原因
關於迴圈,遞迴和棧的一些思考
js實現遞迴,尾遞迴(遞迴優化),防止棧溢位
今天為大家整理了十張動圖GIFS,有助於認識迴圈、遞迴、二分檢索等概念的具體執行情況。程式碼例項以Python語言編寫。
二叉樹遍歷理解——遞迴及非遞迴方法中棧的利用
用遞迴逆序棧
用表儲存代替遞迴演算法
python中,迴圈與遞迴舉例
SQL Server 利用WITH AS遞迴獲取層級關係資料
僅使用遞迴函式和棧操作逆序一個棧
二叉樹遍歷(迴圈和遞迴)
遞迴——以全排列和n皇后問題舉例
遞迴函式對棧記憶體的使用
程式設計3:僅用遞迴函式和棧操作逆序一個棧
如何僅用遞迴函式和棧操作逆序一個棧——你要先用stack實現,再去改成遞迴——需要對遞迴理解很深刻才能寫出來
用mysql儲存過程代替遞迴查詢 用mysql儲存過程代替遞迴查詢
javascript分別用for迴圈和遞迴計算不死神兔
使用遞迴函式實現棧的逆序, 不使用其他資料結構 stack reverse
Java 刷題 -- 棧(棧最小元素/棧構造佇列/遞迴逆序棧/棧排序)(左程雲面試指南)