本文开始我们正式进入关于栈的章节,具体的内容包括但不限于 栈的基本特征、如何来构造一个栈(数组,链表)等。
前面学习的链表啊,数组什么的部分题型都可以使用栈来解决(虽说有点牛头不对马嘴吧,但起码是解出来了 doge),那么我就来看看到底什么是栈吧。
啥是栈?
在计算机科学中,栈(堆栈)是一种抽象的数据类型,用作元素的集合,主要有两个操作:
- PUSH :将元素添加到集合
- POP:删除最近添加但尚未删除的元素
话不多说,直接来个图:
下面来再赖深入的了解一下;
栈也是属于线性表这一结构的,但它是比较特殊的一类,又称为访问受限的线性表。之所以访问受限是因为栈的插入和删除操作都只能在线性表的一端进行。一般来说,把允许操作的一端成为栈顶,反之为栈底。
栈的主要操作
除了上面所说的两种主要(push和pop)的操作,栈还有许多其他的操作,具体的介绍入下:
- 入栈(Push):将元素压入栈顶,即向栈中添加一个新的元素。
- 出栈(Pop):从栈顶弹出一个元素,并返回该元素的值。在出栈之前需要判断栈是否为空,否则会导致栈下溢。
- 查看栈顶元素(Peek):获取栈顶元素的值,但不对栈进行修改。同样需要在查看之前判断栈是否为空,避免栈下溢。
- 判断栈是否为空(isEmpty):检查栈是否为空,即栈中是否还有元素。
- 获取栈的大小(size):获取栈中元素的数量。
Java中的栈是什么样的?
在Java中,可以使用 java.util.Stack
类来表示栈数据结构。Stack
类继承自 Vector
,它是一个动态数组,但它只能通过栈的方式进行访问。这意味着你只能在栈顶进行入栈和出栈操作,而不能在任意位置插入或删除元素。看代码就很容易懂了!
import java.util.Stack;
public class StackExample {
public static void main(String[] args) {
// 创建一个栈对象
Stack<Integer> stack = new Stack<>();
// 入栈操作
stack.push(10);
stack.push(20);
stack.push(30);
// 获取栈顶元素 只显示不出栈
int topElement = stack.peek();
System.out.println("栈顶元素为:" + topElement); // 输出:栈顶元素为:30
// 出栈操作 出栈且显示
int poppedElement = stack.pop();
System.out.println("出栈的元素为:" + poppedElement); // 输出:出栈的元素为:30
// 判断栈是否为空
boolean isEmpty = stack.isEmpty();
System.out.println("栈是否为空:" + isEmpty); // 输出:栈是否为空:false
// 获取栈的大小
int size = stack.size();
System.out.println("栈的大小:" + size); // 输出:栈的大小:2
}
}
栈的实现方式
栈可以使用不同的数据结构来实现,常见的实现方式有以下两种:
使用数组(Array)实现栈:通过数组来保存栈中的元素,同时使用一个指针(通常称为栈顶指针)来指示栈顶的位置。入栈操作时,将元素添加到栈顶指针所指的位置,并将栈顶指针加一;出栈操作时,将栈顶指针减一,并返回栈顶指针所指的元素。
使用链表(Linked List)实现栈:通过链表来保存栈中的元素,每个节点包含一个元素和一个指向下一个节点的指针。栈顶即为链表的头节点,入栈操作时,创建一个新的节点并将其作为新的头节点,并将原头节点连接到新节点的后面;出栈操作时,移除头节点并将头节点指向下一个节点。
这两种实现方式各有优劣。
使用数组实现的栈在空间上比较高效,因为它可以一次性分配好所有的空间,并且不需要频繁地创建和销毁节点。但是,如果栈的大小超过了预先分配的空间,就需要进行扩容操作,这可能导致时间复杂度较高。
使用链表实现的栈在空间上可能相对较慢,因为每个元素都需要一个额外的指针来指向下一个节点。但是,它不需要预先分配空间,可以动态地根据需要创建新的节点,因此在处理大小不确定的情况下比较灵活。
除了数组和链表,栈还可以通过其他数据结构来实现,比如使用动态数组(ArrayList)、动态链表(LinkedList)、双向链表(Doubly Linked List)等。不同的实现方式适用于不同的场景,选择合适的实现方式可以根据具体的需求和性能要求进行权衡。
具体实现
下面就来看看到底是怎么实现的吧~~
使用数组
值得提一嘴的是:top 有的地方指向栈顶元素,有的地方指向栈顶再往上的一个空单位,这个根据题目要求设计,如果是面试的时候就直接问面试官,top门指向到哪里,本文采用指向栈顶空置。
具体代码如下:
public class ArrayStack {
private int[] stackArray;
private int top; // 栈顶指针
public ArrayStack(int capacity) {
stackArray = new int[capacity];
top = -1; // 初始时栈为空,栈顶指针设为-1
}
public boolean isEmpty() {
return top == -1;
}
public boolean isFull() {
return top == stackArray.length - 1;
}
public void push(int value) {
if (isFull()) {
System.out.println("Stack is full. Cannot push element.");
return;
}
top++; // 先将栈顶指针上移一位
stackArray[top] = value; // 将元素压入栈顶位置
}
public int pop() {
if (isEmpty()) {
System.out.println("Stack is empty. Cannot pop element.");
return -1; // 用特定值表示栈为空
}
int value = stackArray[top]; // 获取栈顶元素的值
top--; // 将栈顶指针下移一位,表示弹出元素
return value;
}
public int peek() {
if (isEmpty()) {
System.out.println("Stack is empty. Cannot peek element.");
return -1; // 用特定值表示栈为空
}
return stackArray[top]; // 返回栈顶元素的值,不弹出元素
}
public int size() {
return top + 1; // 栈中元素个数等于栈顶指针+1
}
// 扩容 一般情况下不用考虑,因为我们可以自己分配容量
public void expandCapacity(int size) {
int len = stackArray.length;
if (size > len) {
size = size * 3 / 2 + 1;//每次扩大50%
stackArray = Arrays.copyOf(stackArray, size);
}
}
}
使用链表
public class LinkedStack {
private class Node {
int value;
Node next;
public Node(int value) {
this.value = value;
this.next = null;
}
}
private Node top; // 栈顶指针
public LinkedStack() {
top = null; // 初始时栈为空,栈顶指针为null
}
public boolean isEmpty() {
return top == null;
}
public void push(int value) {
Node newNode = new Node(value); // 创建新节点
newNode.next = top; // 新节点的下一个节点为当前栈顶节点
top = newNode; // 更新栈顶指针为新节点
}
public int pop() {
if (isEmpty()) {
System.out.println("Stack is empty. Cannot pop element.");
return -1; // 用特定值表示栈为空
}
int value = top.value; // 获取栈顶节点的值
top = top.next; // 将栈顶指针下移一位,表示弹出元素
return value;
}
public int peek() {
if (isEmpty()) {
System.out.println("Stack is empty. Cannot peek element.");
return -1; // 用特定值表示栈为空
}
return top.value; // 返回栈顶节点的值,不弹出元素
}
public int size() {
int count = 0;
Node current = top;
while (current != null) {
count++; // 遍历链表,计算栈中元素个数
current = current.next;
}
return count;
}
}
除了这两种方法,还有一种是常用的,那就是 使用 LinkedList 进行实现,就我而言,认为这种方式比较方便一些。
使用LinkedList
代码如下:
import java.util.LinkedList;
public class LinkedStack {
private LinkedList<Integer> stackList;
public LinkedStack() {
stackList = new LinkedList<>();
}
public boolean isEmpty() {
return stackList.isEmpty();
}
public void push(int value) {
stackList.addFirst(value); // 在链表头部添加元素,模拟压栈操作
}
public int pop() {
if (isEmpty()) {
System.out.println("Stack is empty. Cannot pop element.");
return -1; // 用特定值表示栈为空
}
return stackList.removeFirst(); // 在链表头部移除元素,模拟弹栈操作
}
public int peek() {
if (isEmpty()) {
System.out.println("Stack is empty. Cannot peek element.");
return -1; // 用特定值表示栈为空
}
return stackList.getFirst(); // 获取链表头部元素,不移除元素
}
public int size() {
return stackList.size();
}
}
好了,之后会继续分享学习记录!