深入理解二叉樹遍歷:遞歸與棧的雙重視角

  • 二叉樹的遍歷
    • 前序遍歷
    • 中序遍歷
    • 后續遍歷
    • 總結

二叉樹的遍歷

雖然用遞歸的方法遍歷二叉樹實現起來更簡單,但是要想深入理解二叉樹的遍歷,我們還必須要掌握用棧遍歷二叉樹,遞歸其實就是利用了系統棧去遍歷。特此記錄一下如何用雙重視角去看待二叉樹的遍歷,加深一下理解。

前序遍歷

我們從前序遍歷入手,搞懂了一個,其它的也就容易了。

使用遞歸的方法遍歷的話很簡單,代碼如下:

public void preorderTraversal(TreeNode root, List<Integer> result) {if (root == null) {return;}// 訪問根節點result.add(root.val);// 遞歸遍歷左子樹preorderTraversal(root.left, result);// 遞歸遍歷右子樹preorderTraversal(root.right, result);
}

它利用了系統中線程的棧空間,先訪問當前節點,再調用自身方法遞歸地去對左子節點進行前序遍歷,這在線程棧空間中會新增加一個方法。線程也會優先去處理在線程棧上新增的方法。如下圖所示:
在這里插入圖片描述
這里發生的事情是:

  1. 系統為每次方法調用維護一個執行上下文,包含局部變量和返回地址
  2. 當執行到preorder(root.left)時,當前方法的執行被暫停,其狀態被保存在系統棧中
  3. 系統轉而執行左子樹的遍歷,完成后會自動返回到保存的執行點
  4. 繼續執行preorder(root.right)

關鍵點是:系統棧自動保存了"接下來要做什么"的信息。每個節點被處理時,系統知道處理完左子樹后還需要回來處理右子樹。

而使用棧的話,我們只需要按照遞歸遍歷的方式自己創建一個棧模擬著線程棧的方法去遍歷就行。代碼如下:

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;public List<Integer> preorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();if (root == null) return result;Stack<TreeNode> stack = new Stack<>();stack.push(root);while (!stack.isEmpty()) {TreeNode node = stack.pop();result.add(node.val);// 先右后左入棧,這樣出棧時會先處理左子節點if (node.right != null) {stack.push(node.right);}if (node.left != null) {stack.push(node.left);}}return result;
}

這里的關鍵區別是:

  1. 我們只能用棧存儲節點本身,而不能存儲"執行到哪一步"的完整上下文
  2. 棧頂元素是下一個要處理的節點,而不是一個帶有執行狀態的方法調用
  3. 由于棧的后進先出特性,為了先處理左子節點,必須先將右子節點入棧,再將左子節點入棧

系統線程棧和手動實現棧的區別

  1. 系統線程棧的一個棧幀的運行(不包括遞歸調用產生的跳轉)等同于手動實現棧的三件事:1.訪問當前節點 2.入棧右子節點 3.入棧左子節點
  2. 系統線程棧新增了一個棧幀等同于手動實現棧的:出棧一個節點作為當前節點

中序遍歷

遞歸代碼如下:

public void inorderTraversal(TreeNode root, List<Integer> result) {if (root == null) {return;}// 遞歸遍歷左子樹inorderTraversal(root.left, result);// 訪問根節點result.add(root.val);// 遞歸遍歷右子樹inorderTraversal(root.right, result);
}

系統線程棧中一個棧幀的執行過程(不包括遞歸調用產生的跳轉)等同于手動實現棧中的三個操作:

  1. 遞歸訪問左子樹
  2. 訪問當前節點
  3. 遞歸訪問右子樹

手動實現棧代碼如下:

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;public List<Integer> inorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();Stack<TreeNode> stack = new Stack<>();TreeNode current = root;while (current != null || !stack.isEmpty()) {// 將所有左子節點入棧while (current != null) {stack.push(current);current = current.left;}// 處理棧頂節點current = stack.pop();result.add(current.val);// 轉向右子樹current = current.right;}return result;
}

核心思路是先將當前節點及其所有左子節點入棧,然后訪問節點值,再處理右子樹
無法用簡單的"入棧-出棧-訪問"模式表達,需要維護一個current指針跟蹤當前處理節點
關鍵步驟:

  1. 將當前節點及其所有左子節點入棧
  2. 彈出棧頂節點并訪問
  3. 將當前節點切換到右子節點,重復步驟1

后續遍歷

遞歸代碼如下:

public void postorderTraversal(TreeNode root, List<Integer> result) {if (root == null) {return;}// 遞歸遍歷左子樹postorderTraversal(root.left, result);// 遞歸遍歷右子樹postorderTraversal(root.right, result);// 訪問根節點result.add(root.val);
}

系統線程棧中一個棧幀的執行過程等同于手動實現棧中的三個操作:

  1. 遞歸訪問左子樹
  2. 遞歸訪問右子樹
  3. 訪問當前節點

手動實現棧代碼如下:

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;public List<Integer> postorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();if (root == null) return result;Stack<TreeNode> stack1 = new Stack<>();Stack<TreeNode> stack2 = new Stack<>();stack1.push(root);// 先將節點按 根-右-左 的順序放入棧2while (!stack1.isEmpty()) {TreeNode node = stack1.pop();stack2.push(node);if (node.left != null) {stack1.push(node.left);}if (node.right != null) {stack1.push(node.right);}}// 從棧2中彈出的順序就是 左-右-根while (!stack2.isEmpty()) {result.add(stack2.pop().val);}return result;
}

雙棧法
系統線程棧中一個棧幀的執行等同于:

  1. 將當前節點壓入第二個棧(結果棧)
  2. 將左子節點壓入第一個棧(處理棧)
  3. 將右子節點壓入第一個棧(處理棧)

注意:入棧順序是"左-右",這樣處理順序變成"右-左",最終從結果棧彈出時順序為"左-右-根"

單棧法

  1. 需要額外記錄上次訪問的節點
  2. 核心思路是判斷右子樹是否已訪問,決定是訪問節點還是處理右子樹
  3. 由于邏輯較復雜,難以用簡單的等價操作表述

總結

讀者可以根據前序遍歷的思路自行去理解中序遍歷和后序遍歷。重點就是理解系統的線程棧是怎么運作的,以及手動實現的棧是如何保存節點的。搞清楚了這兩點,對二叉樹的遍歷的理解就會更上一層了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/78369.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/78369.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/78369.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Qt Creator中自定義應用程序的可執行文件圖標

要在Qt Creator中為你的應用程序設置自定義可執行文件圖標&#xff0c;你需要按照以下步驟操作&#xff1a; Windows平臺設置方法 準備圖標文件&#xff1a; 創建一個.ico格式的圖標文件&#xff08;推薦使用256x256像素&#xff0c;包含多種尺寸&#xff09; 可以使用在線工…

Windows11系統中GIT下載

Windows11系統中GIT下載 0、GIT背景介紹0.0 GIT概述0.1 GIT誕生背景0.2 Linus Torvalds 的設計目標0.3 Git 的誕生&#xff08;2005 年&#xff09;0.4 Git 的后續發展0.5 為什么 Git 能成功&#xff1f; 1、資源下載地址1.1 官網資源1.2 站內資源 2、安裝指導3、驗證是否下載完…

react的fiber 用法

在 React 里&#xff0c;Fiber 是 React 16.x 及后續版本采用的協調算法&#xff0c;它把渲染工作分割成多個小任務&#xff0c;讓 React 可以在渲染過程中暫停、恢復和復用任務&#xff0c;以此提升渲染性能與響應能力。在實際開發中&#xff0c;你無需直接操作 Fiber 節點&am…

FPGA前瞻篇-數字電路基礎-邏輯門電路設計

模擬信號&#xff1a; 一條隨時間連續變化、平滑波動的曲線&#xff0c;比如正弦波。 數字信號&#xff1a; 一條只有高低兩個狀態&#xff08;0和1&#xff09;&#xff0c;跳變清晰的方波曲線。 在 IC 或 FPGA 的邏輯設計中&#xff0c;我們通常只能處理數字信號&#xff0…

RabbitMQ 基礎概念(隊列、交換機、路由鍵、綁定鍵、信道、連接、虛擬主機、多租戶)介紹

本文是博主在梳理 RabbitMQ 知識的過程中&#xff0c;將所遇到和可能會遇到的基礎知識記錄下來&#xff0c;用作梳理 RabbitMQ 的整體架構和功能的線索文章&#xff0c;通過查找對應的知識能夠快速的了解對應的知識而解決相應的問題。 文章目錄 一、RabbitMQ 是什么&#xff1f…

機器學習第一篇 線性回歸

數據集&#xff1a;公開的World Happiness Report | Kaggle中的happiness dataset2017. 目標&#xff1a;基于GDP值預測幸福指數。&#xff08;單特征預測&#xff09; 代碼&#xff1a; 文件一&#xff1a;prepare_for_traning.py """用于科學計算的一個庫…

Java面試高頻問題(29-30)

二十九、全鏈路壓測&#xff1a;數據隔離與流量 關鍵技術點 1. 流量染色&#xff1a;通過Header注入X-Test-TraceId標識壓測流量 2. 影子庫表&#xff1a;通過ShardingSphere實現數據隔離 3. 熔斷降級&#xff1a;壓測流量觸發異常時自動切換回生產數據源 數據隔離方案對比 …

Python常用的第三方模塊之數據分析【pdfplumber庫、Numpy庫、Pandas庫、Matplotlib庫】

【pdfplumber庫】從PDF文件中讀取內容 import pdfplumber #打開PDF文件 with pdfplumber.open(DeepSeek從入門到精通(20250204).pdf) as pdf:for i in pdf.pages: #遍歷頁print(i.extract_text()) #extract_text()方法提取內容print(f----------------第{i.page_number}頁結束…

長短板理論——AI與思維模型【83】

一、定義 長短板理論思維模型&#xff0c;也被稱為木桶原理&#xff0c;是指一只木桶能盛多少水&#xff0c;并不取決于最長的那塊木板&#xff0c;而是取決于最短的那塊木板。該理論將木桶視為一個整體系統&#xff0c;各個木板代表著系統的不同組成部分或要素&#xff0c;強…

2025藍橋省賽c++B組第二場題解

前言 這場的題目非常的簡單啊&#xff0c;至于為什么有第二場&#xff0c;因為當時河北正在刮大風被迫停止了QwQ&#xff0c;個人感覺是歷年來最簡單的一場&#xff0c;如果有什么不足之處&#xff0c;還望補充。 試題 A: 密密擺放 【問題描述】 小藍有一個大箱子&#xff0…

【數據結構與算法】從完全二叉樹到堆再到優先隊列

完全二叉樹 CBT 設二叉樹的深度為 h , 若非最底層的其他各層的節點數都達到最大個數 , 最底層 h 的所有節點都連續集中在左側的二叉樹叫做 完全二叉樹 . 特點 對任意節點 , 其右分支下的葉子節點的最底層為 L , 則其左分支下的葉子節點的最低層一定是 L 或 L 1 .完全二叉樹…

Leetcode:1. 兩數之和

題目 給定一個整數數組 nums 和一個整數目標值 target&#xff0c;請你在該數組中找出 和為目標值 target 的那 兩個 整數&#xff0c;并返回它們的數組下標。 你可以假設每種輸入只會對應一個答案&#xff0c;并且你不能使用兩次相同的元素。 你可以按任意順序返回答案。 示…

flume整合kafka

需求一&#xff1a; 啟動flume 啟動kafka消費者&#xff0c;驗證數據寫入成功 新增測試數據 需求二&#xff1a; 啟動Kafka生產者 啟動Flume 在生產者中寫入數據

Hbase集群管理與實踐

一、HBase集群搭建實戰 1.1 環境規劃建議 硬件配置基準(以10節點集群為例): 角色CPU內存磁盤網絡HMaster4核16GBSSD 200GB(系統盤)10GbpsRegionServer16核64GB124TB HDD(JBOD)25GbpsZooKeeper4核8GBSSD 500GB10Gbps1.2 關鍵配置項示例(hbase-site.xml) <configu…

STM32 開發 - stm32f10x.h 頭文件(內存映射、寄存器結構體與宏、寄存器位定義、實現點燈案例)

概述 STM32F10x.h 是 STM32F1 系列微控制器的核心頭文件&#xff0c;提供了所有外設寄存器的定義和內存映射 一、內存映射 #define PERIPH_BASE ((uint32_t)0x40000000)#define APB1PERIPH_BASE PERIPH_BASE #define APB2PERIPH_BASE (PERIPH_BASE 0x…

QEMU源碼全解析 —— 塊設備虛擬化(23)

接前一篇文章:QEMU源碼全解析 —— 塊設備虛擬化(22) 本文內容參考: 《趣談Linux操作系統》 —— 劉超,極客時間 《QEMU/KVM源碼解析與應用》 —— 李強,機械工業出版社 特此致謝! QEMU啟動過程中的塊設備虛擬化 上一回解析了qcow2格式對應的qcow2_open函數,本回解…

【PCB工藝】推挽電路及交越失真

推挽電路(Push-Pull Circuit) 推挽電路(Push-Pull Circuit) 是一種常用于功率放大、電機驅動、音頻放大等場合的電路結構,具有輸出對稱、效率高、失真小等優點。 什么是推挽電路? 推挽是指:由兩種極性相反的器件(如 NPN 和 PNP、NMOS 和 PMOS)交替導通,一個“推”電…

RD電子實驗記錄本選用貼士A-B-C

傳統的實驗記錄本&#xff0c;令人又愛又恨本 如何挑選電子實驗室記錄本&#xff08;ELN&#xff09;的品牌/服務商/供應商&#xff1f; 電子實驗記錄本&#xff0c;又名為ELN&#xff0c;Electronic lab notebook&#xff0c;enotebook&#xff0c;研發電子管理系統&#xf…

Qt實戰之將自定義插件(minGW)顯示到Qt Creator列表的方法

Qt以其強大的跨平臺特性和豐富的功能&#xff0c;成為眾多開發者構建圖形用戶界面&#xff08;GUI&#xff09;應用程序的首選框架。而在Qt開發的過程中&#xff0c;自定義插件能夠極大地拓展應用程序的功能邊界&#xff0c;讓開發者實現各種獨特的、個性化的交互效果。想象一下…

java基礎之枚舉和注解

枚舉 簡介 枚舉&#xff1a;enumeration&#xff0c;jdk1.5中引入的新特性&#xff0c;用于管理和使用常量 入門案例 第一步&#xff1a;定義枚舉&#xff0c;這里定義一個動物類&#xff0c;里面枚舉了多種動物 public enum AnimalEnum {CAT, // 貓DOG, // 狗PIG // …