当前位置:首页 > Rust > 正文

Rust回文树详解(从零开始掌握回文自动机的Rust实现)

在字符串处理领域,Rust回文树(也称为回文自动机)是一种高效的数据结构,用于快速统计和查询字符串中所有不同的回文子串。本教程将手把手教你如何用Rust语言实现一个完整的回文树,即使你是Rust初学者也能轻松上手。

什么是回文树?

回文树(Palindromic Tree),又称Eertree,是由Mikhail Rubinchik在2015年提出的一种数据结构。它能够在线性时间内构建,并支持动态添加字符、统计不同回文子串数量、查询最长回文后缀等操作。

Rust回文树详解(从零开始掌握回文自动机的Rust实现) Rust回文树 回文自动机 Rust算法实现 字符串处理Rust 第1张

回文树的核心思想

回文树包含两类节点:

  • 奇根节点:代表长度为-1的虚拟回文串(用于处理奇数长度回文)
  • 偶根节点:代表长度为0的空回文串(用于处理偶数长度回文)

每个节点存储以下信息:

  • 回文串的长度
  • 指向其最长回文后缀的fail指针
  • 子节点映射(通过字符跳转)
  • 出现次数(可选)

Rust实现步骤

下面我们用字符串处理Rust的方式逐步实现回文树。

1. 定义节点结构

#[derive(Default)]struct Node {    len: i32,               // 回文串长度    fail: usize,            // fail指针    next: [usize; 26],      // 子节点(假设只处理小写字母)    count: usize,           // 出现次数}impl Node {    fn new(len: i32) -> Self {        Node {            len,            fail: 0,            next: [0; 26],            count: 0,        }    }}

2. 定义回文树结构

struct PalindromicTree {    nodes: Vec<Node>,    last: usize,            // 当前最长回文后缀对应的节点    s: Vec<u8>,             // 输入字符串的字符数组    n: usize,               // 当前处理到的位置}impl PalindromicTree {    fn new() -> Self {        let mut nodes = Vec::new();        // 节点0:偶根(长度0)        nodes.push(Node::new(0));        // 节点1:奇根(长度-1)        nodes.push(Node::new(-1));                // 奇根的fail指向自己        nodes[1].fail = 1;        // 偶根的fail指向奇根        nodes[0].fail = 1;                PalindromicTree {            nodes,            last: 0,            s: vec![0], // 添加哨兵            n: 0,        }    }}

3. 实现扩展函数

关键在于找到当前位置能形成的新回文串:

impl PalindromicTree {    fn extend(&mut self, c: u8) {        self.s.push(c);        self.n += 1;                let ci = (c - b'a') as usize;        let mut cur = self.last;                // 找到满足 s[n - len[cur] - 1] == s[n] 的cur        while self.s[self.n - self.nodes[cur].len as usize - 1] != c {            cur = self.nodes[cur].fail;        }                if self.nodes[cur].next[ci] != 0 {            // 已存在该回文串            self.last = self.nodes[cur].next[ci];            self.nodes[self.last].count += 1;            return;        }                // 创建新节点        let new_node = self.nodes.len();        self.nodes.push(Node::new(self.nodes[cur].len + 2));        self.nodes[cur].next[ci] = new_node;                // 设置fail指针        if self.nodes[new_node].len == 1 {            // 长度为1,fail指向偶根            self.nodes[new_node].fail = 0;        } else {            // 找到最长回文后缀            let mut tmp = self.nodes[cur].fail;            while self.s[self.n - self.nodes[tmp].len as usize - 1] != c {                tmp = self.nodes[tmp].fail;            }            self.nodes[new_node].fail = self.nodes[tmp].next[ci];        }                self.last = new_node;        self.nodes[self.last].count = 1;    }}

4. 使用示例

fn main() {    let s = "ababa";    let mut tree = PalindromicTree::new();        for c in s.bytes() {        tree.extend(c);    }        // 不同回文子串数量 = 节点数 - 2(减去两个根节点)    println!("不同回文子串数量: {}", tree.nodes.len() - 2);        // 输出所有回文串长度    for (i, node) in tree.nodes.iter().enumerate() {        if i > 1 { // 跳过根节点            println!("回文串长度: {}, 出现次数: {}", node.len, node.count);        }    }}

为什么选择Rust实现回文自动机?

使用Rust算法实现回文树有诸多优势:

  • 内存安全:避免C++中常见的指针错误
  • 零成本抽象:性能接近C/C++
  • 强大的类型系统:编译期捕获错误
  • 所有权模型:自动管理内存,无需垃圾回收

总结

通过本教程,你已经掌握了Rust回文树的基本原理和完整实现。回文自动机是处理回文相关问题的强大工具,在竞赛编程和实际应用中都有广泛用途。建议你动手实践,尝试添加更多功能如输出具体回文串、处理Unicode字符等。

记住,掌握字符串处理Rust技巧不仅能提升算法能力,还能加深对Rust语言特性的理解。继续练习,你将成为Rust算法高手!