栈的简介:
1 栈是一种先入后出的有序列表。
2 栈是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
3 根据栈的定义可知,最先放入栈的元素在栈底,最后放的元素在栈顶。所以,删除元素时,先删除最后放的元素,最早放的元素最晚删。
入栈图解:
出栈图解:
栈的应用场景有:
1 子程序的调用:在跳往子程序前,会将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
2 处理递归调用:和子程序的调用类似,只是出了存储下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
3 逆序输出。
4 表达式的转换[中缀表达式转后缀表达式]与求值
5 二叉树的遍历。
6 图的深度优先(depth-first)搜索法。
7 数制转换:通过求余法,每次将余数进栈,最后将所有余数出栈即可。
8 括号匹配校验
9 迷宫求解
10 实现递归-汉诺塔
下面我们用数组来模拟栈的出栈和入栈等操作:
思路:
1 先定义一个top变量指向栈顶,top初始化为-1.
2 当有入栈操作时,先判断是否栈满,若栈未满,则将top变量自增,将入栈的数据存在数组 Stack[top] 中
3 出栈操作时,先判断是否栈空,若不空,则将 Stack[top] 中的数据输出,并将top变量自减
下面代码实现:
//定义一个 ArrayStack 表示栈
class ArrayStack {
private int maxSize;//栈的大小
private int top=-1;//top表示栈顶,初始化为-1
private int Stack[];// 数组,数组模拟栈,数据就放在该数组
public ArrayStack(int maxSize) {
this.maxSize=maxSize;
Stack=new int [this.maxSize];
}
//栈满
public boolean isFull() {
return top==maxSize-1;
}
//栈空
public boolean isEmpty() {
return top==-1;
}
//入栈-push
public void Push(int value) {
//先判断栈是否满
if(isFull()) {
System.out.println("栈满");
return;
}
top++;
Stack[top] = value;
}
//出栈-pop, 将栈顶的数据返回
public int Pop() {
if(isEmpty()) {
throw new RuntimeException("栈空");
}
int value=Stack[top];
top--;
return value;
}
//显示栈的情况[遍历栈], 遍历时,需要从栈顶开始显示数据
public void showStack() {
if(isEmpty()) {
System.out.println("栈空");
return;
}
for(int i=top;i>=0;i--) {
System.out.printf("stack[%d]=%d\n", i, Stack[i]);
}
}
}
在主方法中做一个菜单来展示一下出栈、入栈和展示:
public class ArrayStackDemo {
public static void main(String[] args) {
//测试一下ArrayStack 是否正确
//先创建一个ArrayStack对象->表示栈
ArrayStack stack = new ArrayStack(4);
String key = "";
boolean loop = true; //控制是否退出菜单
Scanner scanner = new Scanner(System.in);
while(loop) {
System.out.println("show: 表示显示栈");
System.out.println("exit: 退出程序");
System.out.println("push: 表示添加数据到栈(入栈)");
System.out.println("pop: 表示从栈取出数据(出栈)");
System.out.println("请输入你的选择");
key = scanner.next();
switch (key) {
case "show":
stack.showStack();
break;
case "push":
System.out.println("请输入一个数");
int value = scanner.nextInt();
stack.Push(value);
break;
case "pop":
try {
int res = stack.Pop();
System.out.printf("出栈的数据是 %d\n", res);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
case "exit":
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出");
}
}
需要注意的是:
用数组来模拟栈,就算把栈中元素全部取出,可是数组中依旧存在放入栈中的元素,只是top变量下移,没有指向出栈的变量。
栈满后再把栈取空,再入栈的数据,实际上是把数组中上次入栈的数据替换了。
下面我们用链表来模拟栈的出栈和入栈等操作:
思路:
只需要在把单链表添加节点(数据入栈)的过程中采用头插法,那么当出栈时,数据便是按照先进后出的定义进行。
下面代码实现:
//定义一个 Node 表示栈
class Node{
private int num;
private Node next;
public Node(int num) {
this.num=num;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
@Override
public String toString() {
return "Node [num=" + num + "]";
}
}
class LinkStack{
private Node top=new Node(-1);
//判断是否为空
public boolean isEmpty() {
return top.getNum()==-1;
}
//无需判断是否为满,因为单链表可以无限添加节点
//入栈
public void Push(Node value) {
if(top.getNext()==null) {//第一个节点的插入
top.setNext(value);
return;
}
//进单链表时直接使用头插法
value.setNext(top.getNext());
top.setNext(value);
}
//出栈
public void Pop() {
if(top.getNext()==null) {
System.out.println("栈空");
return;
}
System.out.printf("出栈节点为%d \n",top.getNext().getNum());
top=top.getNext();
}
public void Show() {
if(top.getNext()==null) {
System.out.println("栈空");
return;
}
Node temp=top;
while(temp.getNext()!=null) {
System.out.printf("节点为%d \n",temp.getNext().getNum());
temp=temp.getNext();
}
}
}
主方法中运行测试:
public class LinkStackDemo {
public static void main(String[] args) {
Node node1=new Node(1);
Node node2=new Node(3);
Node node3=new Node(2);
Node node4=new Node(4);
LinkStack stack=new LinkStack();
//入栈
stack.Push(node1);
stack.Push(node2);
stack.Push(node3);
stack.Push(node4);
stack.Show();
//测试出栈
stack.Pop();
stack.Pop();
stack.Pop();
stack.Pop();
stack.Pop();//测试栈空时是否能出栈
}
}
输出结果如下:
课后拓展
我发现栈中的 add() 和 push() 方法都能实现把数据压入栈中的效果,那么这两个有何不同呢?
有兴趣的朋友可以看看 Java栈中add()和push()的不同之处