iT邦幫忙

2025 iThome 鐵人賽

DAY 10
0
Rust

用刷題來練RUST系列 第 10

用刷題來練RUST Day 10 Stack 函式呼叫 & 遞迴

  • 分享至 

  • xImage
  •  

我們直接用範例來看函式呼叫和遞迴的關係。

Leetcode 104. Maximum Depth of Binary Tree

題目:給一棵二元樹,計算這顆樹有幾層

輸入:root = [3,9,20,null,null,15,7]

輸出:3

解法1:使用遞迴,先往下走到葉節點時深度為0,再往上走並回傳左右子樹較深的值,直到根節點就能知道最深是多少。

// Definition for a binary tree node.
// #[derive(Debug, PartialEq, Eq)]
// pub struct TreeNode {
//   pub val: i32,
//   pub left: Option<Rc<RefCell<TreeNode>>>,
//   pub right: Option<Rc<RefCell<TreeNode>>>,
// }

// impl TreeNode {
//   #[inline]
//   pub fn new(val: i32) -> Self {
//     TreeNode {
//       val,
//       left: None,
//       right: None
//     }
//   }
// }
use std::rc::Rc;
use std::cell::RefCell;
impl Solution {
    pub fn max_depth(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
        match root {
            None => 0,
            Some(node) => {
                let l_depth = Self::max_depth(node.borrow().left.clone());
                let r_depth = Self::max_depth(node.borrow().right.clone());
                return l_depth.max(r_depth)+1;
            }
        }
    }
}

函式呼叫與 Stack 的關聯

每一次函式呼叫Self::max_depth(),rust都會建立一個「stack frame」

  • 程式執行時會使用一塊記憶體叫「呼叫堆疊(call stack)」。

  • 每當你呼叫一次,系統會把這個函式的「參數、區域變數、返回位址」放進一個stack frame

  • 這個 frame 會放到 call stack 上(像資料結構的 stack.push)

2. 當函式執行結束,stack frame 被彈出(pop)

  • 函式結束後,會從 call stack 中彈出自己的 frame,回到上層函式。

leetcode 104例子 樹大概如下

      3
     / \
    9   20
       /  \
      15   7
1.      max_depth(3)
2.      ├── max_depth(9)
3.      │   ├── max_depth(None)
4.      │   └── max_depth(None)
5.      └── max_depth(20)
6.          ├── max_depth(15)
7.          │   ├── max_depth(None)
8.          │   └── max_depth(None)
9.          └── max_depth(7)
10.              ├── max_depth(None)
11.              └── max_depth(None)

上圖為stack呼叫順序,呼叫後就要往下一層走,直到有回傳值

  1. 呼叫root max_depth(node3)

  2. 呼叫node3左子樹max_depth(node9)找最深深度

  3. 呼叫node9左子樹,因為左子樹的是None回傳0

  4. 呼叫node9右子樹,因為右子樹的是None回傳0

  5. 回傳max_depth(node9)=1

  6. 呼叫node3右子樹max_depth(node20)找最深深度

  7. 呼叫node20左子樹Self::max_depth(node15);

  8. 呼叫node15左子樹,因為左子樹的是None回傳0

  9. 呼叫node15右子樹,因為右子樹的是None回傳0

  10. 回傳max_depth(node15)=1

  11. 呼叫node20右子樹Self::max_depth(node7);

  12. 呼叫node7左子樹,因為左子樹的是None回傳0

  13. 呼叫node7右子樹,因為右子樹的是None回傳0

  14. 回傳max_depth(node7)=1

  15. 回傳max_depth(node20)=2

  16. 回傳max_depth(node3)=3

如果你的遞迴很深(像是深度上萬層的二元樹),你就會遇到

thread 'main' has overflowed its stack

可以看到系統分配的stack爆了,這時可以自己用Vec寫stack來模擬Last In First Out,因為Vec,存放在 heap 上,空間比較大

解法2:使用stack來存節點和深度

use std::rc::Rc;
use std::cell::RefCell;
impl Solution {
    pub fn max_depth(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
        if let None = root {
            return 0
        }
        let mut stack:Vec<(Rc<RefCell<TreeNode>>,i32)> = Vec::new();
        let mut depth = 0;
        stack.push((root.unwrap(),1));

        while let Some((node,current_depth)) = stack.pop() {
            if let Some(left) = node.borrow().left.clone() {
                stack.push((left,current_depth+1));
            }
            if let Some(right) = node.borrow().right.clone() {
                stack.push((right,current_depth+1));
            }
            depth = depth.max(current_depth);
        }
        depth
    }
}

測測試recursion和stack兩個版本

fn generate_deep_tree(depth: i32) -> Option<Rc<RefCell<TreeNode>>> {
    let mut root = Rc::new(RefCell::new(TreeNode::new(0)));
    let mut current = root.clone();

    for i in 1..depth {
        let new_node = Rc::new(RefCell::new(TreeNode::new(i)));
        current.borrow_mut().right = Some(new_node.clone());
        current = new_node;
    }

    Some(root)
}

使用遞迴

結果:stack overflow

use std::rc::Rc;
use std::cell::RefCell;

// 定義二元樹節點
#[derive(Debug)]
pub struct TreeNode {
    pub val: i32,
    pub left: Option<Rc<RefCell<TreeNode>>>,
    pub right: Option<Rc<RefCell<TreeNode>>>,
}

impl TreeNode {
    pub fn new(val: i32) -> Self {
        TreeNode {
            val,
            left: None,
            right: None,
        }
    }
}

pub struct Solution;

impl Solution {
    pub fn max_depth(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
        match root {
            None => 0,
            Some(node) => {
                let l_depth = Self::max_depth(node.borrow().left.clone());
                let r_depth = Self::max_depth(node.borrow().right.clone());
                return l_depth.max(r_depth)+1;
            }
        }
    }
}

fn main() {
    let depth = 1_000_000;
    let root = generate_deep_tree(depth);

    let result = Solution::max_depth(root);
    println!("Max depth = {}", result);
}

fn generate_deep_tree(depth: i32) -> Option<Rc<RefCell<TreeNode>>> {
    let mut root = Rc::new(RefCell::new(TreeNode::new(0)));
    let mut current = root.clone();

    for i in 1..depth {
        let new_node = Rc::new(RefCell::new(TreeNode::new(i)));
        current.borrow_mut().right = Some(new_node.clone());
        current = new_node;
    }

    Some(root)
}



//thread 'main' has overflowed its stack
//fatal runtime error: stack overflow, aborting

使用stack

結果:印出Max depth = 1000000

use std::rc::Rc;
use std::cell::RefCell;

// 定義二元樹節點
#[derive(Debug)]
pub struct TreeNode {
    pub val: i32,
    pub left: Option<Rc<RefCell<TreeNode>>>,
    pub right: Option<Rc<RefCell<TreeNode>>>,
}

impl TreeNode {
    pub fn new(val: i32) -> Self {
        TreeNode {
            val,
            left: None,
            right: None,
        }
    }
}

pub struct Solution;

impl Solution {
    pub fn max_depth(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
        if let None = root {
            return 0
        }
        let mut stack:Vec<(Rc<RefCell<TreeNode>>,i32)> = Vec::new();
        let mut depth = 0;
        stack.push((root.unwrap(),1));

        while let Some((node,current_depth)) = stack.pop() {
            if let Some(left) = node.borrow().left.clone() {
                stack.push((left,current_depth+1));
            }
            if let Some(right) = node.borrow().right.clone() {
                stack.push((right,current_depth+1));
            }
            depth = depth.max(current_depth);
        }
        depth
    }
}

fn main() {
    let depth = 1_000_000;
    let root = generate_deep_tree(depth);

    let result = Solution::max_depth(root);
    println!("Max depth = {}", result);
}

fn generate_deep_tree(depth: i32) -> Option<Rc<RefCell<TreeNode>>> {
    let mut root = Rc::new(RefCell::new(TreeNode::new(0)));
    let mut current = root.clone();

    for i in 1..depth {
        let new_node = Rc::new(RefCell::new(TreeNode::new(i)));
        current.borrow_mut().right = Some(new_node.clone());
        current = new_node;
    }

    Some(root)
}



//Max depth = 1000000

Day10 總結

  1. 遞迴recursion:系統幫你呼叫stack來push、pop。
  2. 疊代iterator:你自己手動寫stack,數量多時手寫可以避免stack overflow。

上一篇
用刷題來練RUST Day 9 用Stack 模擬特定字元處理 & 復原動作
系列文
用刷題來練RUST10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言