本篇文章将详细讲述数据结构 栈与队列 章节的内容。
栈与队列章节部分概念方面比较简单,将放在具体的代码实现过程中。
栈的定义与操作
定义与术语
栈的基本操作
Initstack(s)
// 置 s 为空栈
Push(s,e)
// 元素 e 进栈 s
Pop(s,e
// 元素 e 出栈 s
Gettop(s,e)
// 顶元素拷贝到 e
Empty(s)
// 判断是否为空栈
栈的应用场景:
- 子程序的调用:
在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
如方法中调用方法。
- 处理递归调用:
和子程序调用类似,只是除了存储下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
- 表达式的转换(中缀表达式转后缀表达式)与求值(实际解决)
- 二叉树的遍历
- 图形的深度优先(depth-first)搜索法
栈的存储表示和操作实现
顺序栈(用数组来模拟栈)
实现思路
- 定义一个数组,来模拟栈。
- 定义一个
top
变量表示栈顶,初始化为-1
。
- 入栈:
stack[++top] = data
- 出栈:
return stack[top--]
代码实现
查看代码实现
- 编写
ArrayStack
类
- 编写测试函数
- 测试结果
链式栈(用链表来模拟栈)
- 实现思路
- 定义一个链表来模拟栈,成员变量有:
- 最大存储量:
maxSize
- 栈中元素个数:
size
- 栈顶元素(同时作为表头):
top
- 入栈:
Node temp = top;
top = new Node(value);
top.next = temp;
size++;
- 出栈
Node temp = top;
top = top.next
size--;
return temp.value;
- 代码实现
查看代码实现
- 编写
LinkedListStack
类
- 编写测试函数
- 测试结果
栈的应用
中缀表达式
基本概念
中缀表达式是一个通用的 算术 或 逻辑公式表示方法 。 操作符 是以 中缀形式 处于操作数的 中间 (例: 3 + 4 ),中缀表达式是人们常用的算数表达方式。
中缀表达式就是 常见的运算表达式,如 (3+4)x5-6。
中缀表达式的求值是人类最熟悉的,但是对于计算机来说却不好操作:
- 需要计算运算符的优先级
- 对于中括号来说,笔者想不出实现办法
因此,在计算结果时,往往会将 中缀表达式 转成其他表达式,一般转成后缀表达式。
思路分析
- 代码实现
如上图,代码实现的基本思路为:
- 先扫描字符串,可以通过一个 index 变量来辅助扫描。
- 如果发现是一个 数字 ,直接入栈。
- 如果发现是一个 操作符 ,分一下情况:
- 当 当前操作符 的优先级 大于栈顶符号 :将 当前操作符 入符号栈。
- 当 当前操作符 的优先级 小于或等于栈顶符号 :
- 弹出数栈中的 2 个数值。
- 弹出符号栈顶的元素。
- 2 个数字与符号进行运算。
- 将计算结果压入数栈。
- 将当前操作符压入符号栈。
- 当扫描完毕时:
- 顺序地从数栈中弹出 2 个数。
- 从符号栈中弹出 1 个操作符。
- 将他们进行计算,然后把计算结果压入数栈中。
- 最后数栈中只会存在一个数值,那就是计算结果。
查看代码实现
- 使用前面实现的数组栈来做我们的容器,只增加了一个查看栈顶元素的
peek()
方法。
- 计算器代码实现
后缀表达式
后缀表达式 又称为 逆波兰表达式 ,与前缀表达式类似,只是 运算符 位于 操作数 之后。
后缀表达式求值过程:
- 从 左到右 扫描表达式。
- 遇到 数字 时,将数字压入堆栈。
- 遇到 符号 时,将数字栈栈顶和次顶元素弹出,计算顺序为 次顶元素(符号)栈顶元素。
- 重复上述过程直至剩余最后一个结果。
中缀表达式转后缀表达式
- 初始化两个栈
- 从左到右扫描中缀表达式
- 遇到操作数直接压入 s2
- 遇到运算符时
- 若 s1 为空,或者栈顶元素符号位
(
,则将其压入符号栈 s1
- 若优先级比栈顶运算符 高 ,也将其压入符号栈 s1
- 若优先级比栈顶运算符 低或者相等 ,则弹出 s1栈顶元素,将其压入 s2 中,然后继续循环步骤 4,直至当前符号压入 s1
- 遇到括号时
- 如果是左括号
(
:直接压入 s1
- 如果是右括号
)
:
则依次弹出 s1栈顶的运算符,并压入 s2 直到遇到 左括号 为止,此时将这一对括号 丢弃
- 重复步骤 2 到 5 ,直到表达式的最右端
- 将 s1 的运算符依次弹出并压入 s2 ,结果的 逆序 即为:中缀表达式转后缀表达式的结果
中缀表达式转后缀表达式的举例说明
查看举例
将中缀表达式:1+((2+3)*4)-5
转化为后缀表达式
扫描到的元素 | s2(栈底->栈顶) | s1(栈底->栈顶) | 说明 |
---|
1 | 1 | null | 遇到操作数直接压入 s2 |
+ | 1 | + | s1 为空,直接压入 |
( | 1 | + ( | 运算符为( ,直接压入 |
( | 1 | + ( ( | 运算符为( ,直接压入 |
2 | 1 2 | + ( ( | 遇到操作数直接压入 s2 |
+ | 1 2 | + ( ( + | 栈顶元素为( ,直接压入 s1 |
3 | 1 2 3 | + ( ( + | 遇到操作数直接压入 s2 |
) | 1 2 3 + | + ( | 遇到) 时,依次弹出 s1栈顶的运算符,并压入 s2 直到遇到 左括号 为止,此时将这一对括号 丢弃 |
* | 1 2 3 + | + ( * | 栈顶元素为( ,直接压入s1 |
4 | 1 2 3 + 4 | + ( * | 遇到操作数直接压入 s2 |
) | 1 2 3 + 4 * | + | 遇到) 时,依次弹出 s1栈顶的运算符,并压入 s2 直到遇到 左括号 为止,此时将这一对括号 丢弃 |
- | 1 2 3 + 4 * + | - | 优先级比栈顶运算符 低或者相等 ,则弹出 s1栈顶元素,将其压入 s2 中,然后继续循环步骤 4,直至当前符号压入 s1 |
5 | 1 2 3 + 4 * + 5 | - | 遇到操作数直接压入 s2 |
null | 1 2 3 + 4 * + 5 - | null | 将 s1 的运算符依次弹出并压入 s2 ,结果的 逆序 即为:中缀表达式转后缀表达式的结果 |
代码实现
查看代码实现
- 逆波兰表达式的计算器
实现思路:
- 先将后缀表达式转化为一个 List
- 对这个 List 进行遍历然后进行计算
- 中缀表达式转后缀表达式代码实现
前缀表达式
前缀表达式又称 波兰表达式 ,前缀表达式的 运算符位于操作数之前。
前缀表达式求值过程:
- 从 右到左 扫描表达式。
- 遇到 数字 将数字压入栈。
- 遇到 符号 弹出数字栈的两个元素,与运算符计算,顺序为: 先 弹出的 (符号) 后 弹出的。
- 重复上述过程直至剩余最后一个结果。
中缀表达式转前缀表达式
查看思路
(1) 初始化两个栈:运算符栈 S1 和储存中间结果的栈 S2;
(2) 从右至左扫描中缀表达式;
(3) 遇到操作数时,将其压入 S2;
(4) 遇到运算符时,比较其与 S1 栈顶运算符的优先级:
(4-1) 如果 S1 为空,或栈顶运算符为右括号“)”,则直接将此运算符入栈;
(4-2) 否则,若优先级比栈顶运算符的较高或相等,也将运算符压入 S1;
(4-3) 否则,将 S1 栈顶的运算符弹出并压入到 S2 中,再次转到(4-1)与 S1 中新的栈顶运算符相比较;
(5) 遇到括号时:
(5-1) 如果是右括号“)”,则直接压入 S1;
(5-2) 如果是左括号“(”,则依次弹出 S1 栈顶的运算符,并压入 S2,直到遇到右括号为止,此时将这一对括号丢弃;
(6) 重复步骤(2)至(5),直到表达式的最左边;
(7) 将 S1 中剩余的运算符依次弹出并压入 S2;
(8) 依次弹出 S2 中的元素并输出,结果即为中缀表达式对应的前缀表达式。
代码实现思路与后缀表达式类似,且使用情况较少,故不再详细讲述。
队列
基本概念
- 队列:是一个 有序列表 ,可以用 数组 或 链表 来实现。
- 特点:遵循 先入先出 的原则。
数组模拟队列
声明 4 个变量:
arr
:用来储存数据的数组
maxSize
:该队列的最大容量
front
:队首下标,随着数据输出而改变
rear
:队尾下标,随着数据输入二改变
队列中常用操作分析,以 add ,把数据存入队列为例,思路分析:
- 存入数据后将尾指针往后移:
rear + 1
,前提是当 front == rear
时,队列是空的
- 若尾指针
rear < maxSize - 1
:
- 则将数据存入
rear
所指的数组元素中
- 否则无法存入数据,
rear = maxSize - 1
表示队列满了
查看代码实现
循环队列
基本思路:
front
:队列的第一个元素
rear
:队列最后一个元素的下一个位置
- 队列 满 计算公式:
(rear + 1) % maxSize == front
- 队列 空 计算公式:
rear == front
- 队列中 有效元素个数 计算公式:
(rear + maxSize -front) % maxSize
查看代码实现