栈的定义
栈是一种数据结构,它是一种受限制的线性表,只能在一端添加或删除元素,具有特定的操作规则。栈的特点是先进后出(LIFO,Last In First Out),即最后入栈的元素最先出栈,而最先入栈的元素最后出栈
栈只能在一端删除或者添加元素,这一端称为栈顶
另外一端称为栈底
生活中类似的栈结构
-
在超市购物时,将购买的商品放入购物篮或购物车中,形成一层一层的堆叠,就像是在组织一个临时的栈结构。
-
在餐厅用餐时,将盘子一层一层地叠放在餐桌上,然后按照用餐的顺序依次清理,就像是在处理一个栈结构。
-
在电脑中,浏览器的后退按钮就是一个栈结构,每次点击后退按钮就会返回上一个浏览页面,就像是在操作一个栈结构。
栈的使用:
java已经给我们提供了实现类 Stack
-
empty(): 这个方法通常用于检查栈是否为空。
-
peek(): 返回栈顶的元素但不移除它。
-
pop(): 移除并返回栈顶的元素。
-
push(element): 将指定的元素压入栈顶。
-
search(element): 在栈中搜索指定的元素并返回其位置,其中最顶部的元素位置为1。
代码示例
package stack;
import java.util.Stack;
public class test {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<Integer>();
// 将元素压入栈
stack.push(5);
stack.push(10);
stack.push(15);
// 查看栈顶元素
System.out.println("栈顶元素: " + stack.peek());
// 弹出栈顶元素
int poppedElement = stack.pop();
System.out.println("弹出的元素: " + poppedElement);
// 检查栈是否为空
System.out.println("栈是否为空? " + stack.isEmpty());
// 搜索元素
int index = stack.search(10);
System.out.println("元素10在栈中的位置: " + index);
}
}
运行结果:
模拟栈实现
顺序栈
顺序栈使用数组实现,可以用动态数组arraylist,也可以用静态数组,我这里采用后者
我这里初始化栈顶指针为-1,代表当前栈顶指针永远指向当前元素,有的小伙伴可能会初始化栈 顶指针为0,代表栈顶指针永远指向当前元素的下一个元素
栈的初始化
private int top;//栈顶指针
private int maxSize;//最大长度
private int[] arr;
public ArrayStack() {
this.top = -1; // 栈顶指针
maxSize = 5; // 初始最大长度
arr = new int[maxSize];
}
判断栈是否为空
如果当前栈顶指针等于初始值-1,那这个栈就是空的,这个没有什么好说的,如果刚开始初始化栈顶指针等于0,那么这里就应该判断栈顶指针是否等于0
public boolean isEmpty() {
return top == -1;
}
判断栈是否满
只要当前栈顶指针等于最大长度等于maxsize-1,那么这个栈就满了, 如果刚开始初始化栈顶指针等于0,那么这里就应该判断栈顶指针是否等于maxsize,而不是maxsize-1;
public boolean isFull() {
return top == maxSize - 1;
}
入栈
入栈之前一定要判断栈是否满了,满了就扩容,如果用的是动态数组,就不需要自己扩容,程序会帮你扩容 然后后移指针
public void push(int data) {
// 判断栈满
if (isFull()) {
// 扩容,扩大到原来的两倍
int[] newArray = Arrays.copyOf(arr, 2 * arr.length);
arr = newArray;
maxSize = 2 * maxSize;
}
arr[++top] = data;
}
出栈
出栈的话,一定要先保存当前指针值,然后再前移指针
public int pop() {
if (isEmpty()) {
throw new EmptyStackException();
} else {
int val = arr[top];
top--;
return val;
}
}
取栈顶元素
取栈顶元素和出栈差不多,只不过出栈需要移动栈顶指针
public int peek() {
if (isEmpty()) {
throw new EmptyStackException();
} else {
int val = arr[top];
return val;
}
}
完整代码
class ArrayStack {
private int top;//栈顶指针
private int maxSize;//最大长度
private int[] arr;
public ArrayStack() {
this.top = -1; // 栈顶指针
maxSize = 5; // 初始最大长度
arr = new int[maxSize];
}
// 入栈
public void push(int data) {
// 判断栈满
if (isFull()) {
// 扩容,扩大到原来的两倍
int[] newArray = Arrays.copyOf(arr, 2 * arr.length);
arr = newArray;
maxSize = 2 * maxSize;
}
arr[++top] = data;
}
// 出栈
public int pop() {
if (isEmpty()) {
throw new EmptyStackException();
} else {
int val = arr[top];
top--;
return val;
}
}
//取栈顶元素
public int peek() {
if (isEmpty()) {
throw new EmptyStackException();
} else {
int val = arr[top];
return val;
}
}
// 判断满
public boolean isFull() {
return top == maxSize - 1;
}
// 判断非空
public boolean isEmpty() {
return top == -1;
}
}
链表栈
链表栈和数组实现的栈的基本功能是相似的,都是遵循后进先出(LIFO)的原则。不过,链表栈不需要像数组实现的栈一样进行判断是否已满,也不需要扩容。这是因为链表在内存中可以动态地分配空间,因此可以根据需要动态地增加或减少节点,从而避免了数组实现的栈可能遇到的容量限制和扩容问题。
这里直接上完整代码
我这里用到是泛型类
class LinkedListStack<T> {
private Node<T> head;
// 入栈
public void push(T data) {
// 头插法
head = new Node<>(data, head);
}
// 出栈
public T pop() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
T data = head.data;
head = head.next;
return data;
}
// 打印栈顶元素
public T peek() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
return head.data;
}
// 判断栈为空
public boolean isEmpty() {
return head == null;
}
// 节点类
private static class Node<T> {
T data;
Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
}
}