【算法通關村 Day7】遞歸與二叉樹遍歷

遞歸與二叉樹遍歷青銅挑戰

理解遞歸

遞歸算法是指一個方法在其執行過程中調用自身。它通常用于將一個問題分解為更小的子問題,通過重復調用相同的方法來解決這些子問題,直到達到基準情況(終止條件)。

遞歸算法通常包括兩個主要部分:

  1. 基準情況(也叫遞歸終止條件):當問題規模足夠小,遞歸可以停止,通常返回一個簡單的結果。
  2. 遞歸部分:將問題分解成更小的子問題,并在遞歸過程中調用自身。

為了更清晰地說明遞歸,我給你一個經典的例子:階乘計算。階乘是一個整數和它以下所有整數的乘積。記作:n! = n * (n-1) * ... * 1,而遞歸的數學定義是:

  • n! = n * (n-1)!
  • 基本情況:1! = 10! = 1

下面是一個使用Java編寫的遞歸算法來計算階乘的示例代碼:

class Factorial {public static int factorial(int n){//基準情況if(n == 0 || n == 1){return 1;}//遞歸部分return n * factorial(n - 1);} public static void main(String[] args){int num = 5;int result = factorial(num);System.err.println(num + "! = " + result);}
}

我們之前進行鏈表反轉使用的是迭代法,回顧一下:

public ListNode reverseList(ListNode head) {ListNode prev = null;ListNode curr = head;while (curr != null) {ListNode nextTemp = curr.next; // 臨時保存下一個節點curr.next = prev;              // 反轉當前節點的指針prev = curr;                   // 前移 prev 和 curr 指針curr = nextTemp;}return prev;                       // 返回新的頭結點
}
  • 時間復雜度:O(N),需要遍歷整個鏈表一次。
  • 空間復雜度:O(1),僅使用了固定數量的額外空間。

鏈表反轉同樣可以通過遞歸法實現,

public ListNode reverseList(ListNode head) {// 基準情況if (head == null || head.next == null) {return head;}// 遞歸調用ListNode newHead = reverseList(head.next);// 反轉當前節點和下一個節點的指向head.next.next = head;  // 當前節點的下一個節點指向當前節點head.next = null;       // 當前節點的 next 指向 null// 返回新的頭節點return newHead;
}

通過遞歸方法反轉鏈表簡潔且易于理解,但需注意其空間復雜度較高(O(n)),因為每次遞歸都會增加調用棧的空間消耗。相比之下,迭代法的空間復雜度更低(O(1)),但在代碼可讀性上稍遜于遞歸法。

遞歸與二叉樹遍歷白銀挑戰

二叉樹遍歷的遞歸寫法

遞歸實現二叉樹的前序、中序、后序遍歷的思路是基于樹的深度優先搜索(DFS)。以下是遞歸實現這三種遍歷方式的代碼,并附有解釋:

1. 二叉樹節點定義

首先,定義一個二叉樹節點(TreeNode)類:

static class TreeNode {int val;TreeNode left;TreeNode right;TreeNode(int val) {this.val = val;this.left = null;this.right = null;}
}

2. 前序遍歷(Preorder Traversal)

前序遍歷的順序是:根節點 → 左子樹 → 右子樹

public void preorderTraversal(TreeNode root) {if (root == null) {return;  // 遞歸終止條件}System.out.print(root.val + " ");  // 訪問根節點preorderTraversal(root.left);      // 遞歸遍歷左子樹preorderTraversal(root.right);     // 遞歸遍歷右子樹
}

解釋:

  • 首先訪問根節點,然后遞歸遍歷左子樹,再遞歸遍歷右子樹。

3. 中序遍歷(Inorder Traversal)

中序遍歷的順序是:左子樹 → 根節點 → 右子樹

public void inorderTraversal(TreeNode root) {if (root == null) {return;  // 遞歸終止條件}inorderTraversal(root.left);       // 遞歸遍歷左子樹System.out.print(root.val + " ");  // 訪問根節點inorderTraversal(root.right);      // 遞歸遍歷右子樹
}

解釋:

  • 先遞歸遍歷左子樹,然后訪問根節點,最后遞歸遍歷右子樹。

4. 后序遍歷(Postorder Traversal)

后序遍歷的順序是:左子樹 → 右子樹 → 根節點

public void postorderTraversal(TreeNode root) {if (root == null) {return;  // 遞歸終止條件}postorderTraversal(root.left);     // 遞歸遍歷左子樹postorderTraversal(root.right);    // 遞歸遍歷右子樹System.out.print(root.val + " ");  // 訪問根節點
}

總結

遞歸實現的核心在于每次對樹的左右子樹進行遞歸操作,遞歸的終止條件是節點為空。當節點不為空時,根據遍歷順序訪問當前節點的值。

假設我們有以下的二叉樹:

        1/ \2   3/ \ 4   5

用以下代碼來測試遍歷:

public class BinaryTreeTraversal {public static void main(String[] args) {// 創建二叉樹TreeNode root = new TreeNode(1);root.left = new TreeNode(2);root.right = new TreeNode(3);root.left.left = new TreeNode(4);root.left.right = new TreeNode(5);BinaryTreeTraversal tree = new BinaryTreeTraversal();System.out.println("Preorder Traversal:");tree.preorderTraversal(root);System.out.println("\nInorder Traversal:");tree.inorderTraversal(root);System.out.println("\nPostorder Traversal:");tree.postorderTraversal(root);}
}

輸出結果:

Preorder Traversal:
1 2 4 5 3 Inorder Traversal:
4 2 5 1 3 Postorder Traversal:
4 5 2 3 1 

遞歸與二叉樹遍歷黃金挑戰

二叉樹遍歷的迭代寫法

  • 前序遍歷:通過棧控制順序,根節點先訪問,再左子樹,最后右子樹。
  • 中序遍歷:使用棧模擬遞歸,把左子樹入棧后訪問根節點,再訪問右子樹。
  • 后序遍歷:使用兩個棧,第一個棧負責遍歷節點,第二個棧記錄節點的訪問順序,最后輸出。

這三種迭代實現都利用棧來模擬遞歸過程,棧的先進后出特性在遍歷過程中起到了關鍵作用。

1. 前序遍歷的迭代實現

前序遍歷順序: 根節點 → 左子樹 → 右子樹

前序遍歷的迭代實現我們使用棧來模擬遞歸過程,下面是詳細步驟。

  • 初始化棧: 我們首先將根節點入棧,因為我們從根節點開始遍歷。
  • 循環遍歷:
    1. 每次從棧中彈出一個節點,訪問它的值。
    2. 訪問節點之后,需要按照前序遍歷的規則,先將右子樹入棧,再將左子樹入棧。這樣做的目的是保證左子樹會先被訪問到。
    3. 如果節點有右子樹或左子樹,就按順序將它們入棧,棧是先進后出的結構,所以下次彈出的節點會先訪問到左子樹。
public void preorderTraversal(TreeNode root) {if (root == null) {return;  // 如果樹為空,直接返回}Stack<TreeNode> stack = new Stack<>();  // 創建一個棧來存儲節點stack.push(root);  // 將根節點入棧while (!stack.isEmpty()) {  // 當棧不為空時,繼續循環TreeNode node = stack.pop();  // 彈出棧頂元素(當前節點)System.out.print(node.val + " ");  // 訪問當前節點// 先右子樹入棧,再左子樹入棧,保證左子樹先被訪問if (node.right != null) {stack.push(node.right);  // 如果右子樹不為空,先將右子樹入棧}if (node.left != null) {stack.push(node.left);  // 如果左子樹不為空,再將左子樹入棧}}
}

2. 中序遍歷的迭代實現

中序遍歷順序: 左子樹 → 根節點 → 右子樹

中序遍歷的迭代實現使用一個棧來模擬遞歸過程,具體過程如下。

  • 步驟 1: 我們從根節點開始,逐層將左子樹的節點入棧。棧會保存當前節點,并且我們一直往左走,直到遇到最左的節點。
  • 步驟 2: 如果當前節點為空(說明已經到達葉子節點的左子樹),就彈出棧頂元素并訪問它,訪問完后轉到右子樹。
  • 步驟 3: 訪問完當前節點后,將指針轉向其右子樹,繼續執行類似的過程。
  • 棧的作用: 棧幫助我們記錄從根到最左葉節點的路徑,并確保訪問完左子樹后再訪問根節點,再訪問右子樹。
public void inorderTraversal(TreeNode root) {Stack<TreeNode> stack = new Stack<>();TreeNode current = root;  // 從根節點開始while (current != null || !stack.isEmpty()) {  // 當棧不為空,或者當前節點不為空時,繼續遍歷// 1. 將當前節點及其所有左子樹入棧while (current != null) {stack.push(current);  // 將當前節點入棧current = current.left;  // 然后將當前節點移到左子節點}// 2. 彈出棧頂元素并訪問current = stack.pop();  // 彈出棧頂元素System.out.print(current.val + " ");  // 訪問當前節點// 3. 轉到右子樹current = current.right;  // 處理右子樹}
}

3. 后序遍歷的迭代實現

后序遍歷順序: 左子樹 → 右子樹 → 根節點

后序遍歷的迭代實現稍微復雜一些,因為我們需要逆序訪問根、右子樹、左子樹。為了實現這一點,我們可以使用兩個棧來模擬遞歸過程。

  • 棧 1(stack1): 用來存儲節點,遍歷順序是根 → 右子樹 → 左子樹。我們先將根節點入棧,然后每次彈出棧頂節點并將其左右子樹入棧(右子樹先入棧)。
  • 棧 2(stack2): 用來存儲節點的訪問順序。因為棧是后進先出的,所以訪問的順序是根 → 右子樹 → 左子樹。最終,我們需要從 stack2 中彈出節點,才能得到正確的后序遍歷順序(左子樹 → 右子樹 → 根節點)。
  • 兩個棧的作用: 第一個棧負責遍歷,第二個棧負責記錄節點的訪問順序,最終通過第二個棧實現后序遍歷的輸出。
public void postorderTraversal(TreeNode root) {if (root == null) {return;  // 如果樹為空,直接返回}Stack<TreeNode> stack1 = new Stack<>();  // 用于存儲遍歷的節點Stack<TreeNode> stack2 = new Stack<>();  // 用于存儲節點的訪問順序stack1.push(root);  // 將根節點入棧while (!stack1.isEmpty()) {  // 當 stack1 不為空時繼續循環TreeNode node = stack1.pop();  // 彈出棧頂元素stack2.push(node);  // 將該節點放入 stack2// 先左子樹入棧,再右子樹入棧if (node.left != null) {stack1.push(node.left);}if (node.right != null) {stack1.push(node.right);}}// stack2 中存放的是根、右子樹、左子樹的順序,我們需要反轉輸出while (!stack2.isEmpty()) {System.out.print(stack2.pop().val + " ");  // 彈出 stack2 中的元素并訪問}
}

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

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

相關文章

樸素貝葉斯法

文章目錄 貝葉斯定理樸素貝葉斯法的學習與分類條件獨立假設樸素貝葉斯的后驗概率最大化準則樸素貝葉斯的基本公式 樸素貝葉斯法的參數估計極大似然估計 貝葉斯定理 前置知識&#xff1a;條件概率、全概率、貝葉斯公式 推薦視頻&#xff0c;看完視頻后搜索博客了解先驗概率、后…

《A++ 敏捷開發》- 20 從 AI 到最佳設計

“我們現在推行AIGC&#xff0c;服務端不需要UI交互設計的用AI自動產出代碼&#xff0c;你建議的結對編程、TDD等是否還適用&#xff1f;” 這兩年AI確實很火&#xff0c;是報紙、雜志的熱門話題。例如&#xff0c;HBR雜志從2024年9月至2025年二月份3期&#xff0c;里面有接近一…

GO系列-IO 文件操作

os io 判斷文件是否存在 func fileExist(filePath string) (bool, error) {_, err : os.Stat(filePath)if err nil {return true, nil}if os.IsNotExist(err) {return false, nil}return false, &CheckFileExistError{filePath} } 讀取文件內容 func readFileContext(…

rs485協議、電路詳解(保姆級)

起源 RS-485即Recommended Standard 485 協議的簡寫。1983年被電子工業協會(EIA)批準為一種通訊接口標準. 數據在通信雙方之間傳輸&#xff0c;本質是傳輸物理的電平&#xff0c;比方說傳輸5V的電壓 -1V的電壓信號&#xff0c;這些物理信號在傳輸過程中會受到很多干擾&#x…

JavaWeb-Tomcat服務器

文章目錄 Web服務器存在的意義關于Web服務器軟件Tomcat服務器簡介安裝Tomcat服務器Tomcat服務器源文件解析配置Tomcat的環境變量啟動Tomcat服務器一個最簡單的webapp(不涉及Java) Web服務器存在的意義 我們之前介紹過Web服務器進行通信的原理, 但是我們當時忘記了一點, 服務器…

【愚公系列】《Python網絡爬蟲從入門到精通》008-正則表達式基礎

標題詳情作者簡介愚公搬代碼頭銜華為云特約編輯,華為云云享專家,華為開發者專家,華為產品云測專家,CSDN博客專家,CSDN商業化專家,阿里云專家博主,阿里云簽約作者,騰訊云優秀博主,騰訊云內容共創官,掘金優秀博主,亞馬遜技領云博主,51CTO博客專家等。近期榮譽2022年度…

視覺分析之邊緣檢測算法

9.1 Roberts算子 Roberts算子又稱為交叉微分算法&#xff0c;是基于交叉差分的梯度算法&#xff0c;通過局部差分計算檢測邊緣線條。 常用來處理具有陡峭的低噪聲圖像&#xff0c;當圖像邊緣接近于正45度或負45度時&#xff0c;該算法處理效果更理想。 其缺點是對邊緣的定位…

DuodooBMS源碼解讀之 sale_change模塊

銷售變更模塊用戶使用手冊 一、模塊概述 本擴展模塊主要包含兩個主要的 Python 文件&#xff1a;sale_change/report/sale_change_report.py 和 sale_change/wizard/sale_change_download.py&#xff0c;提供了銷售變更報表查看和銷售變更單下載的功能。以下是詳細的使用說明…

OpenCV形態學操作

1.1. 形態學操作介紹 初識&#xff1a; 形態學操作是一種基于圖像形狀的處理方法&#xff0c;主要用于分析和處理圖像中的幾何結構。其核心是通過結構元素&#xff08;卷積核&#xff09;對圖像進行掃描和操作&#xff0c;從而改變圖像的形狀和特征。例如&#xff1a; 腐蝕&…

力扣算法-1

力扣算法 1 兩數之和 給定一個整數數組nums和一個整數目標值target&#xff0c;請你在數組中找出和為目標值target的那兩個整數&#xff0c;返回他們的數組下標。 &#xff08;1&#xff09;暴力枚舉 &#xff08;枚舉數組每一個數x&#xff0c;再尋找數組中是否存在 targe…

pyside6學習專欄(三):自定義QLabel標簽擴展類QLabelEx

標簽是界面設計中最常用的控件&#xff0c;本文演示了如何基于PySide6的QLabex控件類擴展定義QLabelEX類&#xff0c;以實現更少的編碼完成各種圖像、彩色文本、動畫的加載和顯示&#xff0c;豐富界面顯示 本示例演示了QLabel和其擴展類QLabelEx分別顯示文本、圖像、動畫的使用…

從0到1:固件分析

固件分析 0x01 固件提取 1、從廠商官網下載 例如D-link的固件&#xff1a; https://support.dlink.com/resource/products/ 2、代理或鏡像設備更新時的流量 發起中間人攻擊MITM #啟用IP轉發功能 echo 1 > /proc/sys/net/ipv4/ip_forward#配置iptables&#xff0c;將目…

使用 Spring Boot 和 Canal 實現 MySQL 數據庫同步

文章目錄 前言一、背景二、Canal 簡介三、主庫數據庫配置1.主庫配置2.創建 Canal 用戶并授予權限 四.配置 Canal Server1.Canal Server 配置文件2.啟動 Canal Server 五.開發 Spring Boot 客戶端1. 引入依賴2. 配置 Canal 客戶端3. 實現數據同步邏輯 六.啟動并測試七.注意事項八…

Linux系統配置阿里云yum源,安裝docker

配置阿里云yum源 需要保證能夠訪問阿里云網站 可以先ping一下看看&#xff08;阿里云可能禁ping&#xff0c;只要能夠解析為正常的ip地址即可&#xff09; ping mirrors.aliyun.com腳本 #!/bin/bash mkdir /etc/yum.repos.d/bak mv /etc/yum.repos.d/*.repo /etc/yum.repos…

后端開發:開啟技術世界的新大門

在互聯網的廣闊天地中&#xff0c;后端開發宛如一座大廈的基石&#xff0c;雖不直接與用戶 “面對面” 交流&#xff0c;卻默默地支撐著整個互聯網產品的穩定運行。它是服務器端編程的核心領域&#xff0c;負責處理數據、執行業務邏輯以及與數據庫和其他后端服務進行交互。在當…

銀河麒麟系統安裝mysql5.7【親測可行】

一、安裝環境 cpu&#xff1a;I5-10代&#xff1b; 主板&#xff1a;華碩&#xff1b; OS&#xff1a;銀河麒麟V10&#xff08;SP1&#xff09;未激活 架構&#xff1a;Linux 5.10.0-9-generic x86_64 GNU/Linux mysql版本&#xff1a;mysql-5.7.34-linux-glibc2.12-x86_64.ta…

從零開始學習PX4源碼9(部署px4源碼到gitee)

目錄 文章目錄 目錄摘要1.gitee上創建倉庫1.1 gitee上創建倉庫PX4代碼倉庫1.2 gitee上創建子倉庫2.固件在gitee部署過程2.1下載固件到本地2.2切換本地分支2.3修改.gitmodules內容2.4同步子模塊倉庫地址2.5同步子模塊倉庫地址更新(下載)子模塊3.一級子模塊和二級子模塊的映射關…

【回溯算法2】

力扣17.電話號碼的字母組合 鏈接: link 思路 這道題容易想到用嵌套的for循環實現&#xff0c;但是如果輸入的數字變多&#xff0c;嵌套的for循環也會變長&#xff0c;所以暴力破解的方法不合適。 可以定義一個map將數字和字母對應&#xff0c;這樣就可以獲得數字字母的映射了…

科普:“Docker Desktop”和“Docker”以及“WSL”

“Docker Desktop”和“Docker”這兩個概念既有緊密聯系&#xff0c;又存在一定區別&#xff1a; 一、聯系 核心功能同源&#xff1a;Docker Desktop 本質上是基于 Docker 核心技術構建的。Docker 是一個用于開發、部署和運行應用程序的開源平臺&#xff0c;它利用容器化技術…

Flutter 網絡請求與數據處理:從基礎到單例封裝

Flutter 網絡請求與數據處理&#xff1a;從基礎到單例封裝 在 Flutter 開發中&#xff0c;網絡請求是一個非常常見的需求&#xff0c;比如獲取 API 數據、上傳文件、處理分頁加載等。為了高效地處理網絡請求和數據管理&#xff0c;我們需要選擇合適的工具并進行合理的封裝。 …