用 Rust 实现一棵二叉树

Posted 码农一

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用 Rust 实现一棵二叉树相关的知识,希望对你有一定的参考价值。

用 Rust 写一棵二叉树

树概览

计算机科学中,树是一种可由递归定义的数据结构,它有一个根节点,根节点下包含若干个子树。

当使用孩子兄弟表示法时,每棵树都可以转换为二叉树,因此,一般使用二叉树。

二叉树概览

二叉树有一个根节点,根节点下最多有两个孩子,分别为左孩子、右孩子。

二叉树属于最基本的数据结构之一,此处并不关心其性质、特点等,更多地关注点在于 Rust 实现。

算法实现

不同于其他语言,由于所有权的特性,Rust 在这类数据结构的实现上有些复杂。

例如,在 javascript 中,我们可以非常轻易地构造一棵树:

/** 树节点结构 */
class TreeNode {
  constructor(value, left, right) {
    this.value = value;
    this.left = left;
    this.right = right;
  }
}

/** 构造值为 1, 左孩子、右孩子均为空的节点 */
const root = new TreeNode(1nullnull);

而在 Rust 中,问题就显现出来了,同样的逻辑:

struct TreeNode<T> {
    value: T,
    left: TreeNode<T>,
    right: TreeNode<T>,
}

fn main() {}

此时编译代码,会报错:

recursive type TreeNode has infinite size.

译:TreeNode 是递归类型,其占据了无限的大小。

这是由于 Rust 中要求变量必须有且只有一个所有权拥有者。

而具有 TreeNode 类型的变量已经拥有了 left, right 的所有权,而 leftright 自身又是 TreeNode 结构,这种无限循环自然会报错。

因此,我们可以使用 Rust 中的 Rc 来将 leftright 改变成引用类型。

Rc

Rc 全称 Reference Counted, 即基于引用计数的智能指针,它可以来支持多重所有权。

Rc<T> 类型的实例会维护一个用于记录值引用次数的计数器,当该计数器为零时,则意味着该值可以被安全地清理掉。

因此,我们可以将代码写为:

use std::rc::Rc;

struct TreeNode<T> {
    value: T,
    left: Rc<TreeNode<T>>,
    right: Rc<TreeNode<T>>,
}

此时,再次编译代码,可以编译通过。

下面,我们来考虑如何生成一个节点:

fn main() {
    let root = TreeNode {
        value: 0,
        left: ????,
        right: ????,
    }
}

新的问题出现了,当创建新的节点的时候我们该给 leftright 生成空值?

Option

Rust 中提供了非常优雅的枚举类型 Option, 其值为:

enum Option<T> {
    /// 表示不存在值
    None,
    /// 表示存在值 T
    Some(T),
}

因此,我们最终可以将类型定义为:

use std::rc::Rc;

struct TreeNode<T> {
    value: T,
    left: Option<Rc<TreeNode<T>>>,
    right: Option<Rc<TreeNode<T>>>,
}

此时,我们可以写一棵树:

fn main() {
    /// 可变的根节点
    let mut root = TreeNode {
        value: 0,
        left: None,
        right: None,
    };
    /// 左孩子
    let left_node = TreeNode {
        value: 1,
        left: None,
        right: None,
    };
    /// 右孩子
    let right_node = TreeNode {
        value: 2,
        left: None,
        right: None,
    };
    // 将根节点的左右孩子赋值
    root.left = Some(Rc::new(left_node));
    root.right = Some(Rc::new(right_node));
}

RefCell

在上述树的前提下,考虑下一个问题,如果我们需要通过 root 来修改 root.left.value 的值,需要怎么做。

理所当然地想:

root.left.unwrap().value = 3;
// unwrap 可以将 Some(val) 返回 val.  

然而很遗憾,这样做会报错:

cannot assign to data in an Rc

这是因为,Rc 属于不可变引用。

为此,可以使用 RefCell<T> 来实现内部可变性,它的内部实现使用了 unsafe 代码。

通常而言,改变不可变引用下的数据是被禁止的,但是不得已的情况下,需要使用 unsafe 代码来绕过 Rust 正常的借用规则。

最终,代码为:

use std::{cell::RefCell, rc::Rc};

struct TreeNode<T> {
    value: T,
    left: Option<Rc<RefCell<TreeNode<T>>>>,
    right: Option<Rc<RefCell<TreeNode<T>>>>,
}

fn main() {
    let mut root = TreeNode {
        value: 0,
        left: None,
        right: None,
    };
    let left_node = TreeNode {
        value: 1,
        left: None,
        right: None,
    };
    let right_node = TreeNode {
        value: 2,
        left: None,
        right: None,
    };
    root.left = Some(Rc::new(RefCell::new(left_node)));
    root.right = Some(Rc::new(RefCell::new(right_node)));

    if let Some(ref mut x) = root.left {
        x.borrow_mut().value = 4;
        println!("{}", x.borrow().value); 
        // result: 4
    }
}


以上是关于用 Rust 实现一棵二叉树的主要内容,如果未能解决你的问题,请参考以下文章

如何判断一棵二叉树是完全二叉树

翻转一棵二叉树

建立一棵二叉树,并对其进行遍历(先序、中序、后序),打印输出遍历结果

构造一棵二叉树,并分别输出其先序遍历、中序遍历和后序遍历的结果

算法leetcode|617. 合并二叉树(rust和go的性能是真的好)

算法leetcode|617. 合并二叉树(rust和go的性能是真的好)