前言
栈最鲜明的特点就是后进先出,一碟盘子就是类似这样的结构,最晚放上去的,可以最先拿出来。本文将介绍的是如何自己实现一个栈结构。
栈的操作
栈是一种先进后出(Last-In-First-Out, LIFO)的数据结构,常见操作包括:
1、入栈(Push):将元素压入栈顶。
2、出栈(Pop):弹出栈顶元素并返回其值。
3、查看栈顶元素(Peek):返回栈顶元素的值,但不对栈进行修改。
4、判断栈是否为空(isEmpty):检查栈是否为空,如果栈中没有任何元素,则返回 true;否则返回 false。
5、获取栈的大小(size):返回栈中元素的数量。
6、清空栈(clear):清除栈中的所有元素,使其变为空栈。
这些是栈的基本操作。栈的实际使用还可能涉及其他操作,如遍历栈、搜索特定元素、栈的深度等等。根据具体的需求,你可以针对栈的特性来自定义其他更复杂的操作。
栈的实现
栈是较容易实现的抽象数据结构之一。我们可以选择数组或者链表来实现,它们各有特点,前者容量有限且固定,但操作简单,而后者容量理论上不受限,但是操作并不如数组方便,每次入栈要进行内存申请,出栈要释放内存,稍有不慎便造成内存泄露。本文对两种实现都做介绍。
1、用数组实现栈
/**
* 用数组实现一个栈
*/
public class MyStack {
private int maxSize; //栈的大小
private int[] stackArray; // 数组,模拟一个栈,用于存放
private int top = -1; // 表示栈顶,初始为-1
public MyStack(int maxSize) {
this.maxSize = maxSize;
stackArray = new int[this.maxSize];
}
public void push(int value) {
//判断是否栈满
if(isFull()){
System.out.println("栈满了..");
return;
}
stackArray[++top] = value;
}
//弹出栈顶的元素并返回
public int pop(int value) {
//判断栈是否为空
if(isEmpty()) {
throw new RuntimeException("栈空,没有数据~~");
}
value = stackArray[top--];
return value;
}
//查看栈顶的元素
public int peek() {
//判断栈是否为空
if(isEmpty()) {
throw new RuntimeException("栈空,没有数据~~");
}
return stackArray[top];
}
// 显示栈中的数据-从栈顶开始显示
public void list() {
// 判断是否栈空
if (isEmpty()) {
System.out.println("栈空~~");
return;
}
//从栈顶开始展示数据
for (int i = top; i >= 0; i--) {
System.out.printf("stack[%d]=%d\n", i, stackArray[i]);
}
}
//栈空
public boolean isEmpty() {
return top == -1;
}
//栈满
public boolean isFull() {
return top == (maxSize - 1);
}
}
2、用队列实现栈
3、用链表实现栈
/**
* @Description
* @Author Flag
* @Date: 2021/7/20 8:53
* @Version: 1.0
**/
public class LinkedStackDome {
public static void main(String[] args) {
//测试一下LinkedStack是否正常
//先创建一个ArrayStack对象->表示栈
LinkedStack stack = new LinkedStack(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.show();
break;
case "exit":
scanner.close();
loop = false;
break;
case "push":
System.out.println("请输入一个数字");
int i = scanner.nextInt();
stack.push(i);
break;
case "pop":
try{
int pop = stack.pop();
System.out.println("出栈的数据:"+pop);
} catch (Exception e){
System.out.println(e.getMessage());
}
break;
default:
break;
}
}
}
}
class LinkedStack{
//定义栈的大小
private int maxSize;
//链表模拟栈,
private LinkedStackNode first;
//top表示栈顶,初始化是-1
private int top;
/**
* 构造方法
* @param maxSize 栈的大小
*/
public LinkedStack(int maxSize) {
this.maxSize = maxSize;
top = -1;
}
/**
* 判断栈是否满了
* @return
*/
public boolean isFull(){
return top+1 == maxSize;
}
/**
* 入栈
* @param value 入栈的元素
*/
public void push(int value){
//1、判断栈是否满了
if(this.isFull()){
System.out.println("栈已经满了,不能再添加元素");
return;
}
//2、新创建一个节点,用于添加到链表上
LinkedStackNode node = new LinkedStackNode(value);
//3、将top++,表示链表中的元素新增了一个
top++;
//4、判断头元素是否为null,如果是null,代表是第一个元素,则直接让新元素当第一个元素
if(first == null){
first = node;
return;
}
//5.如果头元素不是null,则证明,此时链表中已经有元素,则将新元素添加上即可
//5.1获取到链表的尾部
LinkedStackNode middleNode = first;
while (middleNode.getNext() != null){
middleNode = middleNode.getNext();
}
//5.2将链表添加上去
middleNode.setNext(node);
}
/**
* 出栈
* @return 出栈的元素
*/
public int pop(){
//1.判断栈是否为null
if(this.isEmpty()){
throw new RuntimeException("栈中没有元素");
}
//2.将链表的数量减一
top -- ;
//3.如果链表中是否只有一个元素
if(first.getNext() == null){
LinkedStackNode popNode = this.first;
this.first = null;
return popNode.getNumber();
}
//4.如果链表中有不只一个元素
//4.1.定义一个中间变量,让他指向链表的最后一个元素,即最后要出栈的元素
LinkedStackNode lastNode = first;
//4.2.定义一个中间变量,用来用来获取到比lastNode前一个元素,‘
//因为是单向链表,我们出栈后,要置空指向最后一个元素的指针,所以需要找到最有一个元素的前一个元素进行操作
LinkedStackNode beforeLastNode = null;
//4.2.遍历链表,直到lastNode是最后一个元素,此时,如果链表中只有一个元素,则
while (lastNode.getNext() != null){
//将lastNode给到beforeLastNode
//然后lastNode向后移动
//此时就构造出 beforeLastNode在lastNode前一个位置的情况
beforeLastNode = lastNode;
lastNode = lastNode.getNext();
}
//4.3.此时将最后一个元素的前一个元素的next指针变成null,则相当于舍弃掉了最后一个元素
beforeLastNode.setNext(null);
//4.3.返回lastNode的编号
return lastNode.getNumber();
}
/**
* 显示栈的元素
*/
public void show(){
//判断栈是否为null
if(this.isEmpty()){
System.out.println("栈中无元素");
return;
}
//定义一个新的链表节点
LinkedStackNode newLinedStackHead = null;
//正向遍历原始链表,将链表的每一个元素,都放到新的链表的第一个元素
//因为前面做了判断,所以first不可以为null
LinkedStackNode oldLinkedStackNode = first;
//直到原始链表元素为null时,结束
while (oldLinkedStackNode != null){
LinkedStackNode middleNode = new LinkedStackNode(oldLinkedStackNode.getNumber());
if(newLinedStackHead == null){
newLinedStackHead = middleNode;
} else {
middleNode.setNext(newLinedStackHead);
newLinedStackHead = middleNode;
}
//移动原始链表的位置
oldLinkedStackNode = oldLinkedStackNode.getNext();
}
while (newLinedStackHead != null){
System.out.println(newLinedStackHead.getNumber());
newLinedStackHead = newLinedStackHead.getNext();
}
}
/**
* 判断栈是否为null
* @return 结果
*/
public boolean isEmpty(){
return top == -1;
}
}
/**
* 链表栈的节点
*/
class LinkedStackNode{
private int number;
private LinkedStackNode next;
public LinkedStackNode(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
public LinkedStackNode getNext() {
return next;
}
public void setNext(LinkedStackNode next) {
this.next = next;
}
}
栈的应用
1、栈的应用——递归
1)、递归的定义
递归是一种重要的程序设计方法。简单地说,若在一个函数、过程或数据结构的定义中又应用了它自身,则这个函数、过程或数据结构称为是递归定义的,简称递归。
它通常把一个大型的复杂问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的代码就可以描述岀解题过程所需要的多次重复计算,大大减少了程序的代码量但在通常情况下,它的效率并不是太高。
2)、斐波那契数列
2、栈的应用——四则运算表达式求值
1)、后缀表达式计算结果
2)、中缀表达式转后缀表达式