栈
文章目录
一、定义
栈是限定仅在表尾进行插入和删除操作的线性表。
我们把允许插入和删除的一端称为栈顶, 另一端称为栈底,不含任何数据元素的栈称为空栈。栈又称为后进先出的线性表,简称 LIFO 结构。
栈的插入操作,叫作进栈,也称压栈、入栈。
栈的删除操作,叫作出栈,也有的叫作弹栈。
二、栈的抽象数据类型
/**
* 栈的抽象数据类型
*
* @author 胡明皓
*/
public abstract class AbstractStack {
Object[] data;
int maxSize;
int top;
/**
* 初始化操作,建立一个栈,并把数据压进栈中
*
* @param data 要压进去的数据
*/
public AbstractStack(Object[] data) {
this.data = data;
}
/**
* 若栈存在,则进行销毁
*/
abstract void destroyStack();
/**
* 将栈清空
*/
abstract void clearStack();
/**
* 若栈为空,返回true,否则返回false
*
* @return true/false
*/
abstract boolean isEmpty();
/**
* 若栈存在且非空,则返回栈顶元素
*
* @return 栈顶元素
*/
abstract Object getTop();
/**
* 若栈存在,插入新元素data到栈中并成为栈顶元素
*
* @param data 新元素
*/
abstract void push(Object data);
/**
* 删除栈中的栈顶元素,并返回其值
*
* @return 栈顶元素
*/
abstract Object pop();
/**
* 返回栈的元素个数
*
* @return 返回栈的元素个数
*/
abstract int length();
}
三、顺序存储结构及实现
3.1、栈的顺序存储结构
3.1.1、定义
栈的顺序存储其实也是线性表顺序存储的简化,简称为顺序栈。
3.1.2、进栈操作
(1)、思路
对于栈的插入,即进栈操作,其实是将新添加的数据元素压到栈中最后一个元素的上面。如下图所示
(2)、代码实现
/**
* 插入元素 e 为新的栈顶元素
*/
void push(Object e) throw Exception {
// 栈满了
if (length() == (maxSize - 1)) {
throw new Exception("栈满");
}
// 栈顶指针加一
top++;
// 将新元素赋值给栈顶空间
data[top] = e;
}
3.1.3、出栈操作
(1)、思路
与进栈操作相反,进栈操作是将数据元素,存放到栈中 t o p + 1 top+1 top+1 的位置。而出栈操作则是将栈中第 top 个位置的元素删除并返回。
(2)、代码实现
/**
* 若栈不空,则删除栈顶元素,并返回元素对象
*/
Object pop() throw Exception {
if (top == -1) {
throw new NullPointException("空栈");
}
Object data = data[top];
top--;
return data;
}
四、两栈共享空间
如果有两个相同类型的栈,为其各自单独开辟了数组空间,极有可能是第一个栈已经满了,再进栈就溢出了,而另一个栈还有很多存储空间空闲。所以,完全可以用一个数组来存储两个栈。
做法如下图
4.1、思路
两个栈在数组的两端,向中间靠拢。top1和top2分别是栈1和栈2的栈顶指针,只要这两个指针不见面,两个栈就可以一直使用。
class DoubleStack {
Object[] data = new Object()[MAX_SIZE];
int top1 = -1;
int top2 = MAX_SIZE + 1;
}
4.2、插入和删除
4.2.1、插入
/**
* 压栈
*
* @param e 压进去的元素
* @param stackIndex 要压进去的栈索引
* @throws Exception 异常
*/
void push(Object e, int stackIndex) throws Exception {
// 如果第一个栈的栈顶指针再加1后等于第二个栈的栈顶指针了,就表示这两个栈已经满了
if ((top1 + 1) == top2) {
throw new Exception("");
}
// 如果栈的索引是1则表示将元素添加到栈1顶,否则则添加到栈2顶
if (stackIndex == 1) {
data[top1++] = e;
} else if (stackIndex == 2) {
data[top2--] = e;
}
}
4.2.2、删除
/**
* 弹栈
*
* @param stackIndex 要压进去的栈索引
* @return 栈顶的元素
* @throws Exception 异常
*/
Object pop(int stackIndex) throws Exception {
if (stackIndex == 1) {
if (top1 == -1) {
// 说明栈1已经是空栈
throw new Exception("");
}
// 将栈1的栈顶元素出栈
return this.data[top1--];
} else if (stackIndex == 2) {
if (top2 == MAX_SIZE) {
// 说明栈2已经是空栈
throw new Exception("");
}
// 将栈2的栈顶元素出栈
return this.data[top2++];
}
return null;
}
五、栈的链式存储结构及实现
5.1、定义
栈的链式存储结构,简称为链栈
class LinkStackNode {
Object data;
LinkStackNode next;
}
class LinkStack {
LinkStackNode top;
int count;
}
5.2、进栈
void push (Object e) {
// 入栈元素分配空间
Element elem = new Element();
// 将元素放入
elem.data = obj;
// 元素指针指向当前top所指元素
elem.next = top;
// 修改top指针
top = elem;
}
5.3、弹栈
Object pop() {
if(this.isEmpty()){
System.out.println("退栈失败,栈为空!");
return null;
}
//取出对象的值
Object obj = top.data;
//修改top指针
top = top.next;
//返回元素
return obj;
}
六、递归
6.1、斐波那契数列实现
如果兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子。假设所有兔子都不死,那么一年后可以繁殖多少对兔子
6.1.2、常规
/**
* 只需要打印出前40次繁殖后的数列
*/
public static void main(String[] args) {
int i;
int[] a = new int[40];
a[0] = 0;
a[1] = 1;
for (i = 2; i < 40; i++) {
a[i] = a[i - 1] + a[i - 2];
}
System.out.println(Arrays.toString(a));
}
6.1.3、递归
static int fbi(int i) {
if (i < 2) {
return i == 0? 0 : 1;
}
return fbi(i - 1) + fbi(i - 2);
}
/**
* 只需要打印出前40次繁殖后的数列
*/
public static void main(String[] args) {
int[] a = new int[40];
for (int i = 0; i < 40; i++) {
a[i] = fbi(i);
}
System.out.println(Arrays.toString(a));
}
七、后缀(逆波兰)表示法
7.1、定义
一种不需要括号的后缀表达法,我们也把它称为逆波兰表示。
9 + ( 3 − 1 ) ∗ 3 + 10 / 2 9 + (3 - 1) * 3 + 10 / 2 9+(3−1)∗3+10/2 —> (9 3 1- 3 * + 10 2 / +)
后面的表达式就是 后缀表达式
7.2、计算结果
后缀表达式:9 3 1- 3 * + 10 2 / +
1、初始化一个空栈。此栈用来对要运算的数字进出使用。
2、后缀表达式中前三个都是数字,所以 9、 3、 1 进栈。
3、接下来是 “ − - − ”,所以将栈中的 1 出栈作为被减数,3 出栈作为减数,并运算 3 − 1 3 - 1 3−1 得 2 ,再将2进栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6TB3deFT-1593476657323)(http://www.wuliang9455.top/blog/assets/img/minus(3, 1)].jpg)
4、接着是数字 3
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0uB9rLoz-1593476657323)(http://www.wuliang9455.top/blog/assets/img/insertStack(3)].jpg)
5、后面是 “ $ * $ ”,也就意味着栈中 3 和 2 出栈,2 与 3 相乘得6,并将 6 进栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wPS970ls-1593476657324)(http://www.wuliang9455.top/blog/assets/img/multiply(3, 2)].jpg)
6、下面是 “ $ + $ ”,所以 6 和 9 出栈,9 与 6 相加得 15 ,将 15 进栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dx2mLzla-1593476657325)(http://www.wuliang9455.top/blog/assets/img/addition(9, 6)].jpg)
7、接着是10 与 2 两数字进栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vDHmDPHI-1593476657325)(http://www.wuliang9455.top/blog/assets/img/insertStack(10, 2)].jpg)
8、接下来是符号 “ $ / $ ” ,因此,栈顶的 2 与 10 出栈,10 与 2 相除得5,将 5 进栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QxxKAaQy-1593476657326)(http://www.wuliang9455.top/blog/assets/img/division(10, 2)].jpg)
9、最后一个是符号 “ $ + $ ”,所以15 与 5 出栈并相加,得 20,将 20 进栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5tPOhPdn-1593476657327)(http://www.wuliang9455.top/blog/assets/img/addition(15, 5)].jpg)
10、结果是 20 出栈,栈变为空。
7.3、中缀表达式转后缀表达式
中缀表达式:“ $ 9 + (3 - 1) * 3 + 10 / 2 $ ” 转化为后缀表达式 “ 9 3 1- 3* + 10 2/ + ”
7.3.1、规则
从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号,则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。
7.3.2、步骤
1、初始化一空栈,用来对符号进出栈使用。
2、第一个字符是数字 9,输出 9,后面是 “ + ”,进栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MO6nCw26-1593476657327)(http://www.wuliang9455.top/blog/assets/img/insertStack(+)].jpg)
3、第三个字符是 “ ( ”,依然是符号,因其只是左符号,还未配对,故进栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BWki5Hzp-1593476657327)(http://www.wuliang9455.top/blog/assets/img/insertStack(()].jpg)
4、第四个字符是数字 3 ,输出,总表达式为 9 3 ,接着是 “ $ - $ ”,进栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-liakWF1W-1593476657328)(http://www.wuliang9455.top/blog/assets/img/insertStack(-)].jpg)
5、接下来是数字 1 , 输出,总表达式为 9 3 1 ,后面是符号 “ $ ) $ ” ,此时,我们需要去匹配此前的 “ $ ($ ” ,所以栈顶依次出栈,并输出,直到 “ $ ( $ ” 出栈为止。此时左括号上方只有 “ $ - $ ”,因此输出 “ $ - $ ”。总的表达式为 9 3 1-。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DruNjWNf-1593476657328)(http://www.wuliang9455.top/blog/assets/img/insertStack(+)].jpg)
6、接着是数字 3 ,输出,总的表达式为:9 3 1- 3。紧接着符号 “ $ * $ ”,因为此时的栈顶符号为 “ $ + $ ”,优先级低于 “ $ * $ ” ,因此不输出,“ $ * $ ” 进栈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n8HCbd9B-1593476657329)(http://www.wuliang9455.top/blog/assets/img/insertStack(multiply)].jpg)
7、之后是符号 “ $ + $ ”,此时当前栈顶元素 “ $ * $ ” 比这个 “ $ + $ ” 的优先级高,因此栈中元素出栈并输出(没有比 “ $ + $ ” 号更低的优先级,所以全部出栈),总表达式为 9 3 1 - 3 * + 。然后将当前这个符号 “ $ + $ ” 进栈。也就是说,前 6 张图的栈底 “ $ + $ ” 是指中缀表达式中开头 9 后面的那个 “ $ + $ ” ,而下图中的 “ $ + $ ” 是指 “ 9 + ( 3 − 1 ) ∗ 3 + 10 / 2 9 + (3-1) *3 + 10/2 9+(3−1)∗3+10/2 ” 中的最后一个 “ $ + $ ” 号。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PniRgyU2-1593476657329)(http://www.wuliang9455.top/blog/assets/img/insertStack(+)].jpg)
8、紧接着是数字10,输出,总表达式变为 9 3 1 - 3 * + 10。后是符号 “ $ / $ ” ,所以 “ $ / $ ” 进栈
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ETTXWDTw-1593476657330)(http://www.wuliang9455.top/blog/assets/img/insertStack(divide)].jpg)
9、最后一个数字 2 ,输出,总的表达式为 9 3 1 - 3 * + 10 2。
10、因已经到最后,所以将栈中符号全部出栈并输出,最后表达式为 9 3 1 - 3 * + 10 2 / +。
7.3.3、总结
1、将中缀表达式转化为后缀表达式(栈用来进出运算的符号)。
2、将后缀表达式进行运算得出结果(栈用来进出运算的数字)。
7.3.4、代码实现
/**
* @author Administrator
*/
public class Main {
static Stack<String> op = new Stack<>();
/**
* 计算每一步运算的结果
*
* @param op 运算符号
* @param f1 被运算的数
* @param f2 运算的数
* @return 运算结果
*/
public static Float getCurrentResult(String op, Float f1, Float f2) {
if ("+".equals(op)) {
return f2 + f1;
} else if ("-".equals(op)) {
return f2 - f1;
} else if ("*".equals(op)) {
return f2 * f1;
} else if ("/".equals(op)) {
return f2 / f1;
} else {
return (float) -0;
}
}
/**
* 计算反向波兰表达式的值
*
* @param rp 反向波兰语表达
* @return 表达的结果
*/
public static float calRp(String rp) {
Stack<Float> v = new Stack<>();
String[] arr = rp.split(" ");
for (String ch : arr) {
// 如果是数字,则推入堆栈
if (String.valueOf(ch).matches("\\d*")) {
v.push((float) (Integer.parseInt(ch)));
} else {
// 如果是运算符,则使用堆栈中前2个操作数计算结果,并将结果压入堆栈
float result = getCurrentResult(ch, v.pop(), v.pop());
System.out.println(result);
v.push(result);
}
}
return v.pop();
}
/**
* 从中缀到后缀
*
* @param s - 中缀表达式
* @return 后缀表达式
*/
public static String getRp(String s) {
String[] arr = s.split(" ");
StringBuilder out = new StringBuilder();
for (String ch : arr) {
if (" ".equals(ch)) {
continue;
}
// 如果是数字,则直接添加到输出字符串中
if (String.valueOf(ch).matches("\\d*")) {
out.append(ch).append(" ");
continue;
}
// 如果为'(',则直接压入堆栈
if ("(".equals(ch)) {
op.push(ch);
}
// 如果是'+'或'-',则从堆栈中弹出运算符,直到'('并添加到输出流中,然后将运算符推入堆栈
if ("+".equals(ch) || "-".equals(ch)) {
while (!op.empty() && (!"(".equals(op.peek()))) {
out.append(op.pop()).append(" ");
}
op.push(ch);
continue;
}
// 如果为'*'或'/',则弹出运算符堆栈并添加到输出流中,直到优先级较低或'('将运算符推入堆栈
if ("*".equals(ch) || "/".equals(ch)) {
while (!op.empty() && ("*".equals(op.peek()) || "/".equals(op.peek()))) {
out.append(op.pop()).append(" ");
}
op.push(ch);
continue;
}
// 如果是')',则将运算符堆栈弹出并添加到输出流中,直到'(',弹出'('
if (")".equals(ch)) {
while (!op.empty() && !"(".equals(op.peek())) {
out.append(op.pop()).append(" ");
}
op.pop();
}
}
while (!op.empty()) {
out.append(op.pop()).append(" ");
}
return out.toString();
}
public static void main(String[] args) {
// 约束:操作数应等于或大于0但等于或小于9
String exp = "9 + ( 3 - 1 ) * 4 + 10 / 2";
float result = calRp(getRp(exp));
System.out.println(result);
}
}