Rust 二叉樹實現與遍歷

二叉樹(Binary Tree) 是非常基礎的數據結構。平衡樹可以讓樹的查找,更新,插入,刪除都是O(logN)的復雜度。

二叉樹的基本實現是比較簡單的。然而對于Rust,因為所有權(ownership)和借用檢查(borrow check) 機制,會比普通的語言稍微"麻煩"一點。leetcode 上有很多二叉樹的題目,本文采用leetcode定義的 tree 節點類型。一方面是 leetcode 的定義可以方便刷題,另一方面會涉及到rust語言特性,可以練習。

Note: leetcode上定義的rust treenode節點,并不是特別match rust的特性,原因參考

本文的主要內容如下:

  • 二叉樹節點定義,使用rust 如何定義二叉樹節點
  • 二叉樹的遍歷,dfs(先序,中序,后序),bfs(層序) 的寫法,遞歸和迭代的異同
  • 二叉樹的樹形打印,方便直觀的看到一顆二叉樹的拓撲形狀
  • 根據一個數組構建一顆二叉樹,即Leetcode二叉樹的輸入,方便離線環境刷題

樹節點定義

Leetcode 幾乎給出了其支持語言的樹節點的定義。如 python 的節點

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

python節點很簡單,定義一個 class,屬性分別是節點值,左右子樹。

golang 的節點

type TreeNode struct {
    Val int
    Left *TreeNode
    Right *TreeNode
}

golang節點也不復雜,定義一個結構體,也是三個字段,分表是數據值,左右子樹的節點指針。之所以左右子樹用指針。TreeNode是遞歸定義,TreeNode如果還沒聲明,是無法在其字段中定義聲明的。

然而 rust的節點定義,卻看起來稍微復雜:

#[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
    }
  }
}

對于 #[derive(Debug, PartialEq, Eq)] 是rust的一種注解,表示默認實現的trait。這里表面這個結構體可以打印(Debug)到控制臺以及可以比較大小(PartialEq, Eq)。

然后定義 TreeNode 節點的時候,有了三層。

  1. Option<T> 一種枚舉,有兩個變體 Some(T) 和 None。有值的時候使用 Some 包裝,無值的時候使用 None。類似 python 的 None 和 golang 的nil,但與它們有本質的區別。
  2. Rc 是一種智能指針,可以共享所有權。rust 的變量和其值是綁定關系,每個值都有管理其所有權的變量。Rc是 (Reference Count)引用計數,它可以有多個所有權引用。但是Rc包裝的內容不可變。
  3. 因為Rc包裝的內容不能改變,而 treenode 會變更其左右子樹,因此 RefCell 提供了內部可變性。

Option比較好理解,下面分別對剩下兩個原因進行代碼演示。為了方便演示,定義下面的簡化結構。

#[derive(Debug)]
struct TreeNode {
    val: i32
}
    let t1 = TreeNode{val:1};
    println!("{:?}", t1); // 輸出: TreeNode { val: 1 }

定義一個 t1 變量,然后綁定一個 TreeNode 實例。通常 tree 的遍歷需要使用棧。

    let t1 = TreeNode{val:1};
    println!("{:?}", t1);

    let mut stack = vec![];
    stack.push(t1);

    println!("{:?}", t1);

讓 t1 進棧,然后再打印 t1。很不幸,rust編譯器將拒絕。原因就是 stack.push(t1),將所有權轉移到了push函數內。t1 變量就等同于"失效"了, println!再使用 t1 就會報錯。下面是 rust 編譯錯誤

17 |     let t1 = TreeNode{val:1};
   |         -- move occurs because `t1` has type `TreeNode`, which does not implement the `Copy` trait
...
21 |     stack.push(t1);
   |                -- value moved here
22 | 
23 |     println!("{:?}", t1);
   |                      ^^ value borrowed here after move

使用 Rc 就能保持多個所有權引用。

    let t1 = Rc::new(TreeNode{val:1});  // 使用 Rc 包裝 TreeNode

    let mut stack = vec![];
    stack.push(t1.clone());             // 增加 t1 的引用計數,其堆上的數據不變。

    println!("{:?}", t1); // TreeNode { val: 1 }
    println!("{:?}", Rc::strong_count(&t1))  // 2  

通過 Rc 可以看到完美的解決了多所有權引用問題。t1.clone() 還可以寫成 Rc::clone(&t1)。后者其實更符合rust習慣,因為可以和其他數據結構的 .clone 方法區分。通常其他結構的 .clone 方法是深復制。而 Rc 的 .clone 是淺復制,只增加 Rc 指針的引用計數,不會復制堆上的數據。

到目前為止,一切順利。實際上,我們對樹節點除了一般的查找,還會更新。也就是會更新 TreeNode內部字段的值。下面的代碼將不會編譯通過:


    let mut t1 = Rc::new(TreeNode{val:1});

    let mut stack = vec![];
    stack.push(t1.clone());

    println!("{:?}", t1); // TreeNode { val: 1 }
    println!("{:?}", Rc::strong_count(&t1));  // 2

    t1.val = 12;
    // t1.deref().val = 12;
    // t1.borrow() = 12;
    // t1.borrow_mut() = 12;


t1.val 可以讀到值。因為 Rc 實現了 Deref trai,會自動解引用。但是不代表可以 t1.val 直接賦值修改。變量 mut t1 的修飾表示 t1 可以重新賦值別的值,但是還是不能修改其自生內部字段的值。同樣的,t1.deref 和 t1.borrow t1.borrow_mut 得到的 &TreeNode ,但是依然都無法修改 val 的值。

想要修改 TreeNode 內部字段的值,就需要 RefCell 結構。

    let t1 = Rc::new(RefCell::new(TreeNode{val:1}));   // 增加 RefCell 包裝

    let mut stack = vec![];
    stack.push(t1.clone());

    println!("{:?}", t1); // TreeNode { val: 1 }
    println!("{:?}", Rc::strong_count(&t1));  // 2

    t1.borrow_mut().val = 12;                 // 通過 borrow_mut 方法獲取 RefMut<TreeNode> 類型,后者可以直接修改 val 的值。

    println!("{:?}", t1.borrow().val);  // 

RefCell 結構通過 borrow/borrow_mut 方法可以得到 Ref/RefMut 類型。后者就等同于 Treenode,可以直接改變其內部字段。并且是 t1 的內部可變,其本身還是 不可變的。

綜上所述,Rc 用于多所有權的引用,RefCell 用于修改 struct 內部字段的值。所以 Leetcode 如此定義 TreeNode。

樹的遍歷

二叉樹的遍歷主要有兩大類,深度優先搜索(DFS)和廣度優先(BFS)。前者是指沿著樹的路徑先遍歷到葉子(Leaf)節點,然后再遍歷其他路徑。后者是指先遍歷每一層的節點,然后層層遞進。

二叉樹有兩個很有用的屬性,節點的高度和深度。高度就是節點到最深葉子節點的距離。其深度是節點到樹根的距離(Leetcode 的定義,實際上路徑數和節點數兩個量都可以)。

深度優先

如同樹節點的定義一樣,二叉樹是節點的集合,一個節點也是二叉樹。二叉樹可以看成:左子樹,根,右子樹的最基本單元。


traversal.png

根據 這三者的遍歷順序可以分為三者遍歷方法。

  • 先序(前序)遍歷(preorder traversal): 根 -> 左子樹 -> 右子樹
  • 中序遍歷(inorder traversal):左子樹 -> 根 -> 右子樹
  • 后序遍歷(inorder traversal):左子樹 -> 右子樹 -> 根

遞歸遍歷

三者遍歷的遞歸寫法很簡單。

先序遍歷

preorder.png

例如下面的 Python3代碼。

def preorder(root: TreeNode):
    # 遞歸基,節點不存在,直接返回
    if root is None:
        return 
    # 先訪問根節點的值
    yield root.val 
    # 遞歸遍歷左子樹,即以左子樹為樹根進行二叉樹遍歷
    yield from preorder(root.left)
    # 遞歸遍歷左子樹,即以左子樹為樹根進行二叉樹遍歷
    yield from preorder(root.left)

之所以這里使用python,是因為py的代碼和算法偽代碼很相似,后面再介紹 rust的實現。

中序遍歷

inorder.png
def inorder(root: TreeNode):
    # 遞歸基,節點不存在,直接返回
    if root is None:
        return 

    # 先遞歸遍歷左子樹,即以左子樹為樹根進行二叉樹遍歷
    yield from inorder(root.left)
    # 訪問根節點的值
    yield root.val 
    # 遞歸遍歷左子樹,即以左子樹為樹根進行二叉樹遍歷
    yield from inorder(root.left)

后序遍歷

postorder.png
def postorder(root: TreeNode):
    # 遞歸基,節點不存在,直接返回
    if root is None:
        return 

    # 先遞歸遍歷左子樹,即以左子樹為樹根進行二叉樹遍歷
    yield from postorder(root.left)

    # 遞歸遍歷左子樹,即以左子樹為樹根進行二叉樹遍歷
    yield from postorder(root.left)

    # 最后訪問根節點的值
    yield root.val 

從上面的代碼可以看出,二叉樹的DFS遍歷的差別只是再訪問根的節點的順序,這本身也是其定義。先根,中根,后根。因此可以統一下面的偽代碼


def tree_order(node):
    if node is None:
        return 

    # preorder vist node
    tree_order(node.left)
    # inorder vist node
    tree_order(node.right)
    # postsorder vist node

rust 實現

leetcode 144. 二叉樹的前序遍歷 是二叉樹的先序遍歷。套用上面介紹的算法,rust代碼如下

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

impl Solution {
    pub fn preorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        fn preorder_dfs(node: &Option<Rc<RefCell<TreeNode>>>, ans: &mut Vec<i32>) {
            // 遞歸基
            if node.is_none(){
                return
            }
            // 訪問根節點
            ans.push(node.as_ref().unwrap().borrow().val);
            // 訪問左子樹
            preorder_dfs(&node.as_ref().unwrap().borrow().left, ans);
            // 訪問右子樹
            preorder_dfs(&node.as_ref().unwrap().borrow().right, ans);
        }
        let mut ans = vec![];
        preorder_dfs(&root, &mut ans);
        ans
    }
}

因為 node 是 &Option<Rc<RefCell<TreeNode>>> 類型,因此需要先提取 option內的 rc,然后通過 borrow 活動 Ref類型的 TreeNode。上面代碼寫法和算法模板一致,但不是 rust 常用的寫法。下面中序遍歷用 rust 模式匹配進行重構

94. 二叉樹的中序遍歷

impl Solution {
    pub fn inorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {

        fn inorder_dfs(node: &Option<Rc<RefCell<TreeNode>>>, ans: &mut Vec<i32>){
            match node {
                None => {},
                Some(x) => {
                    inorder_dfs(&x.borrow().left, ans);
                    ans.push(x.borrow().val);
                    inorder_dfs(&x.borrow().right, ans);
                },

            }
        }

        let mut ans = vec![];
        inorder_dfs(&root, &mut ans);
        ans
    }
}

上面的代碼使用了 match 模式匹配。其寫法依然和算法模板很像。node是 &Option, 通過 match模式匹配,None 相當于 遞歸基,Some 變體解包可以得到 Rc ,進而得到TreeNode 進行訪問值和遞歸。

除了match,rust還提供了便捷的模式匹配語法。下面對145. 二叉樹的后序遍歷 進行說明

impl Solution {
    pub fn postorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        fn postorder_dfs(node: &Option<Rc<RefCell<TreeNode>>>, ans: &mut Vec<i32>) {
            if let Some(x) = node {
                postorder_dfs(&x.borrow().left, ans);
                postorder_dfs(&x.borrow().right, ans);
                ans.push(x.borrow().val);
            }
        }

        let mut ans = vec![];
        postorder_dfs(&root, &mut ans);
        ans
    }
}

使用 if let 方式進行模式匹配,忽略掉匹配失敗的分支,這里是忽略了 None 變體的 &Option結構。推薦使用最后一種方式,簡單明了。

二叉樹的 dfs 的遞歸算法寫法很簡單。并且由于函數傳遞的是 &Option, 在遞歸調用過程中,也不需要引用多個所有權。對于dfs的非遞歸寫法,則需要借助棧。此時就涉及到二叉樹節點的多所有權。

迭代寫法

先序遍歷

python3 的算法模板:

class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        return list(self.preorder(root))

    def preorder(self, node: TreeNode):
        if node is None:
            return
        # 節點進棧
        stack = [node]
        while len(stack) > 0:
            # 出棧
            node = stack.pop()
            # 訪問節點
            yield node.val
            # 右子樹進棧
            if node.right is not None:
                stack.append(node.right)
            # 左子樹進棧
            if node.left is not None:
                stack.append(node.left)

用 Rust 直接翻譯算法代碼如下:

impl Solution {
    pub fn preorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        Solution::preorder_dfs(root)
    }

    fn preorder_dfs(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        let mut ans = vec![];

        if root.is_none() {
            return ans;
        }
        // 進棧
        let mut stack = vec![root];
        while !stack.is_empty() {
            // stack pop的值會自動包裝 Option,需要調用 flatten 打平
            let node = stack.pop().flatten().unwrap();
            // 通過 Rc 的borrow 獲取 Ref<TreeNode> 節點
            let node = node.borrow();
            // 訪問節點值
            ans.push(node.val);
            // 右子樹進棧
            if let Some(ref right) = node.right {
                stack.push(Some(right.clone()));
            }
            // 左子樹進棧
            if let Some(ref left) = node.left {
                stack.push(Some(left.clone()));
            }
        }
        ans
    }
}

由于 Rust 有Option 類型,實際上進棧出棧也可以直接操作,即使遇到子樹不存在的 None,pop出來的時候也可以通過模式匹配處理。代碼會比較緊湊。

impl Solution {
    pub fn preorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        Solution::preorder_dfs(root)
    }

    fn preorder_dfs(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        let mut ans = vec![];
        // 節點進棧,即使 root是None,也沒關系。
        let mut stack = vec![root];
        while !stack.is_empty() {
            // 節點出棧則進行模式匹配,過濾不存在的節點 None
            if let Some(node) = stack.pop().flatten() {
                ans.push(node.borrow().val);
                // 右子樹進棧,None也沒關系
                stack.push(node.borrow().right.clone());
                // 左子樹進棧,None也沒關系
                stack.push(node.borrow().left.clone());
            }
        }
        ans
    }
}

向量 vec pop的結構被包裝了 option。在進行先序遍歷的時候,我們可以保證入棧出棧的節點都有值,就像第一種遍歷的方式,那么可以直接入棧 Rc 結構,而不是 Option結構。

    fn preorder_dfs(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        let mut ans = vec![];

        if root.is_none() {
            return ans;
        }
        // root 節點不是 None, unwrap 安全
        let mut stack = vec![root.unwrap()];
        while !stack.is_empty() {
            if let Some(node) = stack.pop() {
                ans.push(node.borrow().val);

                if let Some(ref right) = node.borrow().right {
                    // 右子樹 Rc 結構進棧,不需要包裝一層 option
                    stack.push(right.clone());
                }
                if let Some(ref left) = node.borrow().left {
                     // 左子樹 Rc 結構進棧
                    stack.push(left.clone());
                }
            }
        }
        ans
    }

上面是rust 實現先序遍歷的幾種寫法,實際問題可以具體考慮。還有一種先序遍歷的算法模板,即左子樹先進棧,進棧前訪問節點。如果是出棧的時候訪問節點,算法則變成中序遍歷。并且后序遍歷的算法結構也可以統一起來。

下面是 python的算法模板:

class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        return list(self.preorder(root))

    def preorder(self, node: TreeNode):
        stack = []
        while True:
            # 左子樹進棧
            while node is not None:
                # 訪問節點
                yield node.val
                # 節點進棧
                stack.append(node)
                # 依次遍歷左子樹
                node = node.left
            # 棧空,退出
            if len(stack) <= 0:
                break
            # 出棧
            # yield node.val 如果在此時訪問 節點,則是中序遍歷
            node = stack.pop()
            # 控制權轉移到右子樹
            node = node.right

上面的算法模板,和遞歸的邏輯很相似。也就是先處理左子樹,然后處理右子樹。下面是 rust 代碼的實現:

impl Solution {
    pub fn preorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        Solution::preorder(root)
    }

    fn preorder(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        let mut ans = vec![];
        let mut stack = vec![];
        let mut node = root;

        loop {
            // 通過模式匹配活動節點的 Rc 引用
            while let Some(x) = node {
                ans.push(x.borrow().val);
                // 節點進棧
                stack.push(x.clone());
                // 轉移左子樹
                node = x.borrow().left.clone();
            }
            if stack.is_empty() {
                break;
            }
            node = stack.pop().unwrap().borrow().right.clone();
        }
        ans
    }
}

代碼和之前的先序遍歷不太一樣,node 會重新賦值,因此它不能借用。需要把所有權轉移給 x,x是 Rc 結構。可以clone增加引用計數。本質上是讓 node重新綁定了新的所有權。

掌握了上面幾種遍歷方式,套用最后一種遍歷方法。下面是中序和后序實現。

中序遍歷

impl Solution {
    pub fn inorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        Solution::inorder(root)
    }

    fn inorder(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        let mut ans = vec![];
        let mut stack = vec![];

        let mut node = root;
        loop {
            while let Some(x) = node {
                stack.push(x.clone());
                node = x.borrow().left.clone();
            }
            if stack.is_empty() {
                break;
            }
            if let Some(x) = stack.pop() {
                ans.push(x.borrow().val);
                node = x.borrow().right.clone();
            }
        }
        ans
    }
}

后序遍歷

impl Solution {
    pub fn postorder_traversal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {
        Solution::postorder(root)
    }

    fn postorder(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<i32> {

        let mut ans = vec![];
        let mut stack = vec![];
        let mut node = root;
        let mut visited = None;

        loop {
            while let Some(x) = node {
                stack.push(x.clone());
                node = x.borrow().left.clone();
            }
            if stack.is_empty() {
                break;
            }
            if stack.last().unwrap().borrow().right != visited {
                node = stack.last().unwrap().borrow().right.clone();
                visited = None
            } else {
                if let Some(x) = stack.pop(){
                    ans.push(x.borrow().val);
                    visited = Some(x.clone());
                }
            }
        }
        ans
    }
}

至此,二叉樹的DFS遍歷方法介紹完畢,下面再看下廣度搜索層序遍歷

廣度優先

層序遍歷如其字面意思,先遍歷二叉樹的每一層所有節點,然后再向下推進一層。與先序遍歷類似,層序遍歷只需要借助一個 隊列 queue。

102. 二叉樹層序遍歷

impl Solution {
    pub fn level_order(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<Vec<i32>> {
        let mut ans = vec![];

        let mut queue = VecDeque::new();
        queue.push_back(root);

        while !queue.is_empty() {
            let size = queue.len();
            let mut level = vec![];
            for _ in 0..size {
                if let Some(x) = queue.pop_front().flatten() {
                    let node = x.borrow();
                    level.push(node.val);
                    queue.push_back(node.left.clone());
                    queue.push_back(node.right.clone());
                }
            }
            if !level.is_empty() {
                ans.push(level);
            }
        }
        ans
    }
}

二叉樹反序列化

二叉樹的層序遍歷有很多用法。Leetcode 的test case 使用的是二叉樹的層序遍歷的數組,進行重建二叉樹。下面就介紹這個方法。

#[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,
        }
    }

    pub fn get_height(root: &Option<Rc<RefCell<TreeNode>>>) -> i32 {
        fn dfs(root: &Option<Rc<RefCell<TreeNode>>>) -> i32 {
            match root {
                None => 0,
                Some(node) => {
                    let node = node.borrow();
                    1 + max(dfs(&node.left), dfs(&node.right))
                }
            }
        }
        dfs(&root)
    }
    
    // 通過數組反序列化生成一棵樹
    pub fn create(nums: Vec<Option<i32>>) -> Option<Rc<RefCell<Self>>> {
        if nums.is_empty() {
            return None;
        }
        let size = nums.len();
        let mut index = 0;
        let root = Some(Rc::new(RefCell::new(Self::new(nums[0].unwrap()))));
        let mut queue = VecDeque::new();
        queue.push_back(root.clone());
        while !queue.is_empty() {
            let q_size = queue.len();
            for _i in 0..q_size {
                if let Some(x) = queue.pop_front().flatten() {
                    let mut node = x.borrow_mut();
                    let lseq = 2 * index + 1;
                    let rseq = 2 * index + 2;
                    if lseq < size && nums[lseq].is_some() {
                        node.left = Some(Rc::new(RefCell::new(Self::new(nums[lseq].unwrap()))));
                        queue.push_back(node.left.clone());
                    }

                    if rseq < size && nums[rseq].is_some() {
                        node.right = Some(Rc::new(RefCell::new(Self::new(nums[rseq].unwrap()))));
                        queue.push_back(node.right.clone());
                    }
                }
                index += 1;
            }
        }
        root
    }
    
    // 將一棵樹序列化成一個數組
    pub fn literal(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<Option<i32>> {
        if root.is_none() {
            return vec![];
        }
        let mut ans = vec![];
        let mut queue = VecDeque::new();
        queue.push_back(root);
        while !queue.is_empty() {
            let qsize = queue.len();
            for _ in 0..qsize {
                match queue.pop_front().flatten() {
                    Some(x) => {
                        ans.push(Some(x.borrow().val));
                        queue.push_back(x.borrow().left.clone());
                        queue.push_back(x.borrow().right.clone());
                    }
                    None => ans.push(None),
                }
            }
        }
        let size = ans.len();
        for i in (0..size).rev() {
            if ans[i].is_none() {
                ans.pop();
            } else {
                break;
            }
        }
        ans
    }
}

二叉樹樹形打印

有了二叉樹的反序列化,可以方便的構造test case。通常為了直觀驗證一棵樹,可以把樹的拓撲樹形進行打印。打印二叉樹需要使用二叉樹樹高,因為滿二叉樹的寬度等于 width = (1<<height) - 1

leetcode 655. 輸出二叉樹

pub fn print_tree(root: Option<Rc<RefCell<TreeNode>>>) -> String {
    // 二叉樹高度
    let height = TreeNode::get_height(&root);
    // 滿二叉樹的寬度
    let width = (1 << height) - 1;
    let mut ans = vec![vec![" ".to_string(); width as usize]; height as usize];

    // dfs 搜索
    fn dfs(ans: &mut Vec<Vec<String>>, node: &Option<Rc<RefCell<TreeNode>>>, deep: usize, lo: usize, hi: usize) {
        if let Some(x) = node {
            let node = x.borrow();
            let mid = lo + (hi - lo) / 2;
            ans[deep][mid] = x.borrow().val.to_string();
            dfs(ans, &node.left, deep + 1, lo, mid);
            dfs(ans, &node.right, deep + 1, mid + 1, hi);
        }
    }

    dfs(&mut ans, &root, 0usize, 0usize, width as usize);
    // 將所有字符連起來
    ans.iter().map(|x| x.concat()).collect::<Vec<_>>().join("\n")
}

上述打印的樹形二叉樹并沒有 path 的邊。需要變可以自行添加。

總結

通過rust構造二叉樹,需要使用到 rust的特性,如所有權,借用檢查,內部可變性等概念。另外掌握二叉樹的基本性質和遍歷方法,可以解決很多 leetcode上的題目。最后,通過二叉樹的反序列化和樹形打印,對刷題和學習驗證相關算法都很有幫助。

更多 Rust 與Leetcode可以參考這個文檔

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容