色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

如何將前中后序的遞歸框架改寫成迭代形式

算法與數據結構 ? 來源:labuladong ? 作者:labuladong ? 2022-03-18 10:13 ? 次閱讀

之前經常講涉及遞歸的算法題,我說過寫遞歸算法的一個技巧就是不要試圖跳進遞歸細節,而是從遞歸框架上思考,從函數定義去理解遞歸函數到底該怎么實現。

而且我們前文學習數據結構和算法的框架思維特別強調過,練習遞歸的框架思維最好方式就是從二叉樹相關題目開始刷,前文也有好幾篇手把手帶你刷二叉樹和二叉搜索樹的文章:

之前的文章全部都是運用二叉樹的遞歸框架,幫你透過現象看本質,明白二叉樹的各種題目本質都是前中后序遍歷衍生出來的。

前文BFS 算法框架詳解是利用隊列迭代地遍歷二叉樹,不過使用的是層級遍歷,沒有遞歸遍歷中的前中后序之分。

由于現在面試越來越卷,很多讀者在后臺問我如何將前中后序的遞歸框架改寫成迭代形式。

首先我想說,遞歸改迭代從實用性的角度講是沒什么意義的,明明可以寫遞歸解法,為什么非要改成迭代的方式?

對于二叉樹來說,遞歸解法是最容易理解的,非要讓你改成迭代,頂多是考察你對遞歸和棧的理解程度,架不住大家問,那就總結一下吧。

我以前見過一些迭代實現二叉樹前中后序遍歷的代碼模板,比較短小,容易記,但通用性較差。

通用性較差的意思是說,模板只是針對「用迭代的方式返回二叉樹前/中/后序的遍歷結果」這個問題,函數簽名類似這樣,返回一個TreeNode列表:

Listtraverse(TreeNoderoot);

如果給一些稍微復雜的二叉樹問題,比如最近公共祖先二叉搜索子樹的最大鍵值和,想把這些遞歸解法改成迭代,就無能為力了。

而我想要的是一個萬能的模板,可以把一切二叉樹遞歸算法都改成迭代

換句話說,類似二叉樹的遞歸框架:

voidtraverse(TreeNoderoot){
if(root==null)return;
/*前序遍歷代碼位置*/
traverse(root.left);
/*中序遍歷代碼位置*/
traverse(root.right);
/*后序遍歷代碼位置*/
}

迭代框架也應該有前中后序代碼的位置:

voidtraverse(TreeNoderoot){
while(...){
if(...){
/*前序遍歷代碼位置*/
}
if(...){
/*中序遍歷代碼位置*/
}
if(...){
/*后序遍歷代碼位置*/
}
}
}

我如果想把任何一道二叉樹遞歸解法改成迭代,直接把遞歸解法中前中后序對應位置的代碼復制粘貼到迭代框架里,就可以直接運行,得到正確的結果。

理論上,所有遞歸算法都可以利用棧改成迭代的形式,因為計算機本質上就是借助棧來迭代地執行遞歸函數的。

所以本文就來利用「棧」模擬函數遞歸的過程,總結一套二叉樹通用迭代遍歷框架

遞歸框架改為迭代

參加過我的二叉樹專項訓練的讀者應該知道,二叉樹的遞歸框架中,前中后序遍歷位置就是幾個特殊的時間點:

前序遍歷位置的代碼,會在剛遍歷到當前節點root,遍歷root的左右子樹之前執行;

中序遍歷位置的代碼,會在在遍歷完當前節點root的左子樹,即將開始遍歷root的右子樹的時候執行;

后序遍歷位置的代碼,會在遍歷完以當前節點root為根的整棵子樹之后執行。

0320d1fc-93aa-11ec-952b-dac502259ad0.jpg

如果從遞歸代碼上來看,上述結論是很容易理解的:

voidtraverse(TreeNoderoot){
if(root==null)return;
/*前序遍歷代碼位置*/
traverse(root.left);
/*中序遍歷代碼位置*/
traverse(root.right);
/*后序遍歷代碼位置*/
}

不過,如果我們想將遞歸算法改為迭代算法,就不能從框架上理解算法的邏輯,而要深入細節,思考計算機是如何進行遞歸的

假設計算機運行函數A,就會把A放到調用棧里面,如果A又調用了函數B,則把B壓在A上面,如果B又調用了C,那就再把C壓到B上面……

C執行結束后,C出棧,返回值傳給BB執行完后出棧,返回值傳給A,最后等A執行完,返回結果并出棧,此時調用棧為空,整個函數調用鏈結束。

我們遞歸遍歷二叉樹的函數也是一樣的,當函數被調用時,被壓入調用棧,當函數結束時,從調用棧中彈出。

那么我們可以寫出下面這段代碼模擬遞歸調用的過程:

//模擬系統的函數調用棧
Stackstk=newStack<>();

voidtraverse(TreeNoderoot){
if(root==null)return;
//函數開始時壓入調用棧
stk.push(root);
traverse(root.left);
traverse(root.right);
//函數結束時離開調用棧
stk.pop();
}

如果在前序遍歷的位置入棧,后序遍歷的位置出棧,stk中的節點變化情況就反映了traverse函數的遞歸過程(綠色節點就是被壓入棧中的節點,灰色節點就是彈出棧的節點):

03342018-93aa-11ec-952b-dac502259ad0.gif

簡單說就是這樣一個流程:

1、拿到一個節點,就一路向左遍歷(因為traverse(root.left)排在前面),把路上的節點都壓到棧里

2、往左走到頭之后就開始退棧,看看棧頂節點的右指針,非空的話就重復第 1 步

寫成迭代代碼就是這樣:

privateStackstk=newStack<>();

publicListtraverse(TreeNoderoot){
pushLeftBranch(root);

while(!stk.isEmpty()){
TreeNodep=stk.pop();
pushLeftBranch(p.right);
}
}

//左側樹枝一擼到底,都放入棧中
privatevoidpushLeftBranch(TreeNodep){
while(p!=null){
stk.push(p);
p=p.left;
}
}

上述代碼雖然已經可以模擬出遞歸函數的運行過程,不過還沒有找到遞歸代碼中的前中后序代碼位置,所以需要進一步修改。

迭代代碼框架

想在迭代代碼中體現前中后序遍歷,關鍵點在哪里?

當我從棧中拿出一個節點p,我應該想辦法搞清楚這個節點p左右子樹的遍歷情況

如果p的左右子樹都沒有被遍歷,那么現在對p進行操作就屬于前序遍歷代碼。

如果p的左子樹被遍歷過了,而右子樹沒有被遍歷過,那么現在對p進行操作就屬于中序遍歷代碼。

如果p的左右子樹都被遍歷過了,那么現在對p進行操作就屬于后序遍歷代碼。

上述邏輯寫成偽碼如下:

privateStackstk=newStack<>();

publicListtraverse(TreeNoderoot){
pushLeftBranch(root);

while(!stk.isEmpty()){
TreeNodep=stk.peek();

if(p的左子樹被遍歷完了){
/*******************/
/**中序遍歷代碼位置**/
/*******************/
//去遍歷p的右子樹
pushLeftBranch(p.right);
}

if(p的右子樹被遍歷完了){
/*******************/
/**后序遍歷代碼位置**/
/*******************/
//以p為根的樹遍歷完了,出棧
stk.pop();
}
}
}

privatevoidpushLeftBranch(TreeNodep){
while(p!=null){
/*******************/
/**前序遍歷代碼位置**/
/*******************/
stk.push(p);
p=p.left;
}
}

有剛才的鋪墊,這段代碼應該是不難理解的,關鍵是如何判斷p的左右子樹到底被遍歷過沒有呢?

其實很簡單,我們只需要維護一個visited指針,指向「上一次遍歷完成的根節點」,就可以判斷p的左右子樹遍歷情況了

下面是迭代遍歷二叉樹的完整代碼框架

//模擬函數調用棧
privateStackstk=newStack<>();

//左側樹枝一擼到底
privatevoidpushLeftBranch(TreeNodep){
while(p!=null){
/*******************/
/**前序遍歷代碼位置**/
/*******************/
stk.push(p);
p=p.left;
}
}

publicListtraverse(TreeNoderoot){
//指向上一次遍歷完的子樹根節點
TreeNodevisited=newTreeNode(-1);
//開始遍歷整棵樹
pushLeftBranch(root);

while(!stk.isEmpty()){
TreeNodep=stk.peek();

//p的左子樹被遍歷完了,且右子樹沒有被遍歷過
if((p.left==null||p.left==visited)
&&p.right!=visited){
/*******************/
/**中序遍歷代碼位置**/
/*******************/
//去遍歷p的右子樹
pushLeftBranch(p.right);
}
//p的右子樹被遍歷完了
if(p.right==null||p.right==visited){
/*******************/
/**后序遍歷代碼位置**/
/*******************/
//以p為根的子樹被遍歷完了,出棧
//visited指針指向p
visited=stk.pop();
}
}
}

代碼中最有技巧性的是這個visited指針,它記錄最近一次遍歷完的子樹根節點(最近一次pop出棧的節點),我們可以根據對比p的左右指針和visited是否相同來判斷節點p的左右子樹是否被遍歷過,進而分離出前中后序的代碼位置。

PS:visited指針初始化指向一個新 new 出來的二叉樹節點,相當于一個特殊值,目的是避免和輸入二叉樹中的節點重復。

只需把遞歸算法中的前中后序位置的代碼復制粘貼到上述框架的對應位置,就可以把任意遞歸的二叉樹算法改寫成迭代形式了

比如,讓你返回二叉樹后序遍歷的結果,你就可以這樣寫:

privateStackstk=newStack<>();

publicListpostorderTraversal(TreeNoderoot){
//記錄后序遍歷的結果
Listpostorder=newArrayList<>();
TreeNodevisited=newTreeNode(-1);

pushLeftBranch(root);
while(!stk.isEmpty()){
TreeNodep=stk.peek();

if((p.left==null||p.left==visited)
&&p.right!=visited){
pushLeftBranch(p.right);
}

if(p.right==null||p.right==visited){
//后序遍歷代碼位置
postorder.add(p.val);
visited=stk.pop();
}
}

returnpostorder;
}

privatevoidpushLeftBranch(TreeNodep){
while(p!=null){
stk.push(p);
p=p.left;
}
}

當然,任何一個二叉樹的算法,如果你想把遞歸改成迭代,都可以套用這個框架,只要把遞歸的前中后序位置的代碼對應過來就行了。

迭代解法到這里就搞定了,不過我還是想強調,除了 BFS 層級遍歷之外,二叉樹的題目還是用遞歸的方式來做,因為遞歸是最符合二叉樹結構特點的

說到底,這個迭代解法就是在用棧模擬遞歸調用,所以對照著遞歸解法,應該不難理解和記憶。

原文標題:二叉樹八股文:遞歸改迭代通用模板

文章出處:【微信公眾號:算法與數據結構】歡迎添加關注!文章轉載請注明出處。

審核編輯:湯梓紅
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 算法
    +關注

    關注

    23

    文章

    4607

    瀏覽量

    92835
  • 模板
    +關注

    關注

    0

    文章

    108

    瀏覽量

    20560
  • 函數
    +關注

    關注

    3

    文章

    4327

    瀏覽量

    62571

原文標題:二叉樹八股文:遞歸改迭代通用模板

文章出處:【微信號:TheAlgorithm,微信公眾號:算法與數據結構】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    labview遞歸使用你嘗試過嗎?

    遞歸VI factorial.vi 例子通過當前數與當前數-1的階乘相乘而得,也就是調用了自己。 數學公式如下,3!=3*(2!)。在這個遞歸factorial VI,1!和0!
    發表于 01-05 15:07

    用Kei寫程序的時候,怎么頭文件為AT89X51.H的程序改寫成...

    用Kei寫程序的時候,怎么頭文件為AT89X51.H的程序改寫成頭文件為REG51.H的。這兩種頭文件寫程序有什么區別?l
    發表于 11-15 21:10

    《C Primer Plus》讀書筆記——遞歸

    ("LEVEL %d: n location %p\n" , n, &n);}輸出如下:遞歸的基本原理每級遞歸都使用其私有變量(如例子的n)每次函數調用都返回一級(調用他那級
    發表于 02-05 20:06

    快速掌握Python的遞歸函數與匿名函數調用

    factorial函數調用factorial函數的情況。出現了函數的遞歸。為了完善上述代碼,可以代碼的第二部也翻譯成代碼:  def factorial(n):  1. int
    發表于 07-19 16:22

    LabVIEW遞歸調用

    一.NI提供的遞歸調用使用的步驟如下1.VI設置成重載模式2.使用靜態調用調用調用VI,實現自身調用看見下圖NI自帶遞歸方法二、如果靜態調用改成直接調用自身也可得到相同的結果,而且
    發表于 05-18 10:36

    遞歸最小二乘法

    一、遞歸最小二乘法遞推最小二乘法:當矩陣維數增加時,矩陣求逆運算計算量過大,而且不適合在線辨識。為了減少計算量,并且可以實時地辨識出動態系統的特性,可以最小二乘法轉換成參數遞推的估計。取N組數據
    發表于 08-27 07:03

    LabVIEW中使用遞歸算法

    ?Open VI Reference。遞歸VI路徑連上,并將options的輸入設置為8,這樣可以使通過引用調用可重入VI有效。在Open VI Reference圖標的type specifier VI
    發表于 04-17 20:11

    C++教程之函數的遞歸調用

    C++教程之函數的遞歸調用 在執行函數 f 的過程,又要調用 f 函數本身,稱為函數的遞歸調用;形式上:一個正在執行的函數調用了自身;這種遞歸
    發表于 05-15 18:00 ?35次下載

    如何將IP模塊整合到System Generator for DSP

    了解如何將Vivado HLS設計作為IP模塊整合到System Generator for DSP。 了解如何將Vivado HLS設計保存為IP模塊,并了解如何將此IP輕松整合
    的頭像 發表于 11-20 05:55 ?3236次閱讀

    如何把C++的源程序改寫成C語言

    第一種是C++的面向對象特征去掉,先全部理解源代碼的邏輯,然后改寫;第二種是在C中保留面向對象的部分特征,用結構體實現類的功能。
    的頭像 發表于 05-14 10:08 ?2917次閱讀
    如何把C++的源程序<b class='flag-5'>改寫成</b>C語言

    C語言編程如何求出二叉樹后序遍歷

    題目 已知二叉樹前序為 ABDFGCEH 后序序列為 BFDGACEH ,要求輸出后序遍歷為 FGDBHECA 大體思路 又先序得出根,先序的根后為左樹一部分,我們再在序序列里找到先序的根,此處
    的頭像 發表于 08-23 11:04 ?3916次閱讀

    如何求遞歸算法的時間復雜度

    那么我通過一道簡單的面試題,模擬面試的場景,來帶大家逐步分析遞歸算法的時間復雜度,最后找出最優解,來看看同樣是遞歸,怎么就寫成了O(n)的代碼。
    的頭像 發表于 07-13 11:30 ?2257次閱讀

    Python支持遞歸函數

    Python支持遞歸函數——即直接或間接地調用自身以進行循環的函數。遞歸是頗為高級的話題,并且它在Python相對少見。然而,它是一項應該了解的有用的技術,因為它允許程序遍歷擁有任意的、不可預知的形狀的結構。
    的頭像 發表于 02-21 14:28 ?643次閱讀

    如何將Pytorch自訓練模型變成OpenVINO IR模型形式

    本文章依次介紹如何將Pytorch自訓練模型經過一系列變換變成OpenVINO IR模型形式,而后使用OpenVINO Python API 對IR模型進行推理,并將推理結果通過OpenCV API顯示在實時畫面上。
    的頭像 發表于 06-07 09:31 ?1985次閱讀
    <b class='flag-5'>如何將</b>Pytorch自訓練模型變成OpenVINO IR模型<b class='flag-5'>形式</b>

    遞歸神經網絡結構形式主要分為

    結構形式。 Elman網絡 Elman網絡是一種基本的遞歸神經網絡結構,由Elman于1990年提出。其結構主要包括輸入層、隱藏層和輸出層,其中隱藏層具有時間延遲單元,可以存儲一時刻的隱藏狀態。Elman網絡的基本原理是
    的頭像 發表于 07-05 09:32 ?518次閱讀
    主站蜘蛛池模板: 被cao的奶水直喷高H| 日日干夜夜啪蕉视频| se01国产短视频在线观看| 欧美色图一区二区三区| FREE乌克兰嫩交HD| 日韩精品在线看| 国产精品资源在线观看网站| 亚洲乱亚洲乱妇在线观看| 久久国产精品永久网站| 511麻豆视传媒精品AV| 青青草狠狠干| 国产精品日本无码久久一老A| 亚洲 日本 欧美 中文字幕| 九九热伊人| 99国产这里只有精品视频| 乳欲性高清在线| 国产在线综合色视频| 最近日本MV字幕免费观看视频| 欧美阿v在线免播播放| 国产XXXXXX农村野外| 亚洲男人在线观看| 美女视频黄a视频全免费网站色窝| xfplay 无码专区 亚洲| 午夜福利08550| 久久精品手机观看| www国产av偷拍在线播放| 天美传媒麻豆精品| 久久本道久久综合伊人| beeg日本老师| 胸大美女又黄的网站| 乱VODAFONEWIFI熟妇| 国产51麻豆二区精品AV视频| 亚洲热在线视频| 欧美精品高潮呻吟AV久久 | 九九99亚洲精品久久久久| 2022一本久道久久综合狂躁| 日本无吗高清| 精品国产乱码久久久久久人妻 | 小学生偷拍妈妈视频遭性教育| 久久这里有精品| 公和熄洗澡三级中文字幕|