Rust 数据结构与算法:3栈:用栈实现符号匹配

1、符号匹配

如: (5+6)×(7+8)/(4+3)、{ { ( [ ] [ ])}}、(a+b)(c*d)func() 等各类语句的符号匹配。

这里我们关注的不是数字而是括号,因为括号更改了操作优先级,限定了语言的语义,这是非常重要的。如果括号不完整,那么整个表达式就是错的。

括号都必须以成对匹配的形式出现。括号匹配意味着每个开始符号都有相应的结束符号,并且括号必须正确嵌套,这样计算机才能正确处理。

真正具有挑战的是如何编写一个算法来从左到右读取一串符号,并决定括号是否匹配。处理的第一个左开始括号必须等待,直到其匹配最后一个右关闭括号为止。结束符号以相反的顺序匹配开始符号,从内到外,这是一个可以用栈来解决的问题。


括号匹配

具体算法原理:

一旦采用栈来保存括号,算法的具体实现就很简单了,因为栈的操作无非就是出入栈和判断而已。

从空栈开始,从左到右处理括号字符串。如果一个符号是开始符号,将其入栈;

如果是结束符号,则弹出栈顶元素并开始匹配这两个符号。

如果它们恰好是左右匹配的,就继续处理下一个括号,直到字符串处理完为止。

最后,当所有符号都被处理后,栈应该是空的。只要栈不为空,就说明有括号不匹配。

检测包含任意字符的字符串是否匹配的代码:

rust 复制代码
#[derive(Debug)]
struct Stack<T> {
    size: usize,  // 栈大小
    data: Vec<T>, // 栈数据
}

impl<T> Stack<T> {
    // 初始化空栈
    fn new() -> Self {
        Self {
            size: 0,
            data: Vec::new(), // 以 Vec 为低层
        }
    }

    fn is_empty(&self) -> bool {
        0 == self.size
    }

    fn len(&self) -> usize {
        self.size
    }

    // 清空栈
    fn clear(&mut self) {
        self.size = 0;
        self.data.clear();
    }

    // 将数据保存在 Vec 的末尾
    fn push(&mut self, val: T) {
        self.data.push(val);
        self.size += 1;
    }

    // 将栈顶减 1 后,弹出数据
    fn pop(&mut self) -> Option<T> {
        if 0 == self.size {
            return None;
        };
        self.size -= 1;
        self.data.pop()
    }

    // 返回栈顶数据引用和可变引用
    fn peek(&self) -> Option<&T> {
        if 0 == self.size {
            return None;
        }
        self.data.get(self.size - 1)
    }

    fn peek_mut(&mut self) -> Option<&mut T> {
        if 0 == self.size {
            return None;
        }
        self.data.get_mut(self.size - 1)
    }

    // 以下是为栈实现的迭代功能
    // into_iter: 栈改变,成为迭代器
    // iter: 栈不变,得到不可变迭代器
    // iter_mut:栈不变,得到可变迭代器

    fn into_iter(self) -> IntoIter<T> {
        // into_iter()方法获取了一个迭代器,然后进行迭代
        IntoIter(self)
    }

    fn iter(&self) -> Iter<T> {
        let mut iterator = Iter { stack: Vec::new() };
        for item in self.data.iter() {
            iterator.stack.push(item);
        }
        iterator
    }

    fn iter_mut(&mut self) -> IterMut<T> {
        let mut iterator = IterMut { stack: Vec::new() };
        for item in self.data.iter_mut() {
            iterator.stack.push(item);
        }
        iterator
    }
}
// 实现三种迭代功能
struct IntoIter<T>(Stack<T>);
// Iterator 是 Rust 的迭代器 迭代器(iterator)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
impl<T: Clone> Iterator for IntoIter<T> {
    // into_iter()方法获取了一个迭代器,然后进行迭代。
    type Item = T;
    fn next(&mut self) -> Option<Self::Item> {
        // 迭代器之所以成为迭代器,是因为实现了Iterator trait。要实现该特征,最主要的就是实现其中的 next 方法,该方法控制如何从集合中取值,最终返回值的类型是关联类型 Item。
        if !self.0.is_empty() {
            self.0.size -= 1;
            self.0.data.pop()
        } else {
            None
        }
    }
}
// 'a 生命周期标识 用于帮助编译器检查引用的有效性,避免悬垂引用和使用已被释放的内存。
// 从所有权的角度来理解,就是它可以避免因为Copy或者clone的造成的不必要开销
struct Iter<'a, T: 'a> {
    stack: Vec<&'a T>, // 'a 被用在了传参类型 T 上
}
impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T; // 生命周期标识只作用于引用上,且放在&符号之后 如这里的 &'a T
    fn next(&mut self) -> Option<Self::Item> {
        self.stack.pop()
    }
}

struct IterMut<'a, T: 'a> {
    stack: Vec<&'a mut T>,
}
impl<'a, T> Iterator for IterMut<'a, T> {
    type Item = &'a mut T;
    fn next(&mut self) -> Option<Self::Item> {
        self.stack.pop()
    }
}

fn main() {
    let sa = "(2+3){func}[abc]";
    let sb = "(2+3)*(3-1";
    let sc = "{{([])}}";
    let sd = "((())";
    let se = "[[[]]]]]]]]]";

    let res1 = par_checker3(sa);
    let res2 = par_checker3(sb);
    let res3 = par_checker3(sc);
    let res4 = par_checker3(sd);
    let res5 = par_checker3(se);

    println!("sa balanced: {res1}");
    println!("sb balanced: {res2}");
    println!("sc balanced: {res3}");
    println!("sd balanced: {res4}");
    println!("se balanced: {res5}");
}

// 同时检测多种开始符号和结束符号是否匹配
fn par_match(open: char, close: char) -> bool {
    let opens = "([{";
    let closers = ")]}";
    opens.find(open) == closers.find(close)
}
// 基于栈的符号匹配
fn par_checker3(par: &str) -> bool {
    let mut char_list = Vec::new();
    for c in par.chars() {
        char_list.push(c);
    }

    let mut index = 0;
    let mut balance = true;
    let mut stack = Stack::new();
    while index < char_list.len() && balance {
        let c = char_list[index];
        // 将开始符号入栈
        if '(' == c || '[' == c || '{' == c {
            stack.push(c);
        }
        // 如果是结束符号,则判断是否平衡
        if ')' == c || ']' == c || '}' == c {
            if stack.is_empty() {
                balance = false;
            } else {
                let top = stack.pop().unwrap();
                if !par_match(top, c) {
                    balance = false;
                }
            }
        }
        // 非括号字符直接跳过
        index += 1;
    }
    balance && stack.is_empty()
}

运行结果:

相关推荐
c1assy几秒前
DP动态规划+贪心题目汇总
数据结构·算法·leetcode·贪心算法·动态规划
jjjxxxhhh1234 分钟前
C++ 模板是为了解决啥问题
开发语言·c++·算法
gz94564 分钟前
Virtualbox安装ubuntu20虚拟机无法打开终端
java·linux·开发语言
c++初学者ABC9 分钟前
GESP2级2403 小杨的日字矩阵
c++·算法
奔跑的犀牛先生18 分钟前
C#学习1:初接触,C#的一些基础,和相关报错
开发语言·c#
大写-凌祁28 分钟前
2024国赛A题第一问
线性代数·算法·机器学习·数学建模
代码小将34 分钟前
PTA数据结构编程题7-1最大子列和问题
数据结构·c++·笔记·学习·算法
半夏知半秋1 小时前
lua debug相关方法详解
开发语言·学习·单元测试·lua
Andy01_1 小时前
Java八股汇总【MySQL】
java·开发语言·mysql
pk_xz1234561 小时前
R 和 Origin 完成细菌 OTU 表、土壤理化性质数据的微生物 Beta 多样性分析
算法·机器学习·r语言