二、基础数据结构
1、线性数据结构
数组、栈、队列、双端队列、链表这类数据结构都是保存数据的容器,数据项之间的顺序由添加或删除时的顺序决定,数据项一旦被添加,其相对于前后元素就会一直保持位置不变,诸如此类的数据结构被称为线性数据结构。线性数据结构有两端,称为“左”和“右”,在某些情况下也称为“前”和“后”,当然也可以称为顶部和底部,名称不重要,重要的是这种命名展现出的位置关系表明了数据的组织方式是线性的。这种线性特性和内存紧密相关,因为内存就是一种线性硬件,由此也可以看出软件和硬件是如何关联在一起的。线性数据结构说的并非数据的保存方式,而是数据的访问方式。线性数据结构不一定代表数据项在内存中相邻。以链表为例,虽然其中的数据项可能在内存的各个位置,但访问是线性的。
2、栈
栈就是一种特别有用的线性数据结构,可用于函数调用、网页数据记录等。栈是数据项的有序集合,其中,新项的添加和移除总发生在同一端,这一端称为顶部,与之相对的另一端称为底部。栈的底部很重要,因为栈中靠近底部的项是存储时间最长的,最近添加的项最先被移除。这种排序原则有时被称为后进先出(Last In First Out,LIFO)或先进后出(First In Last Out,FILO),所以较新的项靠近顶部,较旧的项靠近底部。
栈的例子很常见,工地上堆的砖,桌子上摆的书,餐厅里摞在一起的盘子,它们都是栈的物理模型。要想拿到最下面的砖、书、盘子,就必须先把上面的都拿走。
1、栈的抽象数据类型
栈的抽象数据类型由栈的结构和操作定义。如前所述,栈被构造为项的有序集合,其中,项的添加和移除位置被称为顶部。栈的部分操作如下。
●new():创建一个空栈,不需要参数,返回一个空栈。
●push(item):将数据项item添加到栈顶,需要item作为参数,不返回任何内容。
●pop():从栈中删除顶部的数据项,不需要参数,返回数据项,栈被修改。
●peek():从栈中返回顶部的数据项但不删除,不需要参数,不修改栈。
●is_empty():测试栈是否为空,不需要参数,返回布尔值。
●size():返回栈中数据项的数量,不需要参数,返回一个usize型整数。
●iter():返回栈的不可变迭代形式,栈不变,不需要参数。
●iter_mut():返回栈的可变迭代形式,栈可变,不需要参数。
●into_iter():改变栈为可迭代形式,栈被消费,不需要参数。
2、栈的 Rust 代码实现
这里使用集合容器Vec作为栈的底层实现,因为Rust中的Vec提供了有序集合机制和一组操作方法,只需要选定Vec的哪一端是栈顶就可以实现其他操作了。以下栈实现假定Vec的尾部保存了栈的顶部元素,随着栈不断增长,新项将被添加到Vec的末尾。因为不知道所插入数据的类型,所以采用泛型数据类型T。此外,为了实现迭代功能,这里添加了IntoIter、Iter、IterMut三个结构体,以分别完成三种迭代功能。
代码:
/** @Description:* @Author: tianyw* @Date: 2024-02-15 14:13:35* @LastEditTime: 2024-02-15 14:56:07* @LastEditors: tianyw*/
#[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 +=