【校招VIP】Java数据结构之栈详解

08月04日 收藏 0 评论 1 java开发

【校招VIP】Java数据结构之栈详解

转载声明:文章链接:https://blog.csdn.net/weixin_51375190/article/details/122489128

栈的定义:
栈(stack)是一种用于存储数据的简单数据结构。栈一个有序线性表,只能在表的一端(PS:栈顶)执行插人和删除操作。最后插人的元素将被第一个删除。所以,栈也称为后进先出(Last In First Out,LIFO)或先进后出(First In Last Out,FILO)线性表。

Java 集合框架中的 Stack 继承自 Vector:
由于 Vector 有 4 个构造函数,加上 Stack 本身的一种,也就是说有 5 中创建 Stack 的方法
跟 Vector 一样,它是可以由数组实现的栈。
栈的几种主要基本操作:
void push(int data):入栈(将数据data插入到栈中)
int pop():出栈(删除并返回最后一个插入栈的元素)
int top():返回最后一个插入栈的元素,但不删除
int size():返回存储在栈中的元素个数
boolean isEmpty():返回栈是否是空栈
boolean isFull():返回是否是满栈
void Clear():清除整个栈
栈的应用:
符号匹配
HTML和XML文件中的标签匹配(实质还是符号匹配)
实现函数调用
文本编辑器中的撤销
网页浏览器中已访问页面的历史记录
作为一个算法的辅助数据结构
栈的基本方法:

public interface Stack<E> extends Iterable<E> {

//获取栈的size大小
public int size();

//判断栈是否为空
public boolean isEmpty();

//入栈 进栈一个元素 在线性表的表尾添加一个元素
public void push(E element);

//出栈 弹出一个元素 在线性表的表尾删除一个元素
public E pop();

//查看当前栈顶元素 并不是移除 查看线性表中最后一个元素
public E peek();

//对当前栈进行清空
public void clear();
}

栈的几种实现方式
1.基于简单数组的实现方式
2.基于动态数组的实现方式
3.基于链表的实现方式
4.基于队列的实现方式

1.基于简单数组的实现:

基于简单数组实现一个栈,其基本思路是这样的:从左至右向数组中添加所有的元素,并定义一个变量用来记录数组当前栈顶(top)元素的下标。当数组存满了栈元素时,执行入栈(插入元素)操作将抛出栈满异常;当对一个没有存储栈元素的数组执行出栈(删除元素)操作将抛出栈空异常,当然,这种实现方式有一个很明显的局限性。那就是:栈的最大空间必须预先声明且不能改变。试图对一个满栈执行入栈操作将会产生异常。
2.基于动态数组的实现:
基于动态数组的实现一个栈,其基本思路跟上面类似。不同的是,这种方式下当数组中存储的元素达到一定量时(如:数组空间的0.75或者整个数组空间),这时我们通常会选择新建一个比原数组空间大一倍的新数组,然后将原数组按照原来的顺序复制进去,接着便可以继续进行入栈操作了。

import p1.Stack;(上方的Stack包)

import java.util.Iterator;

public class ArrayStack<E> implements Stack<E> {

//定义私有变量List
private ArrayList<E> list;

//默认构造
public ArrayStack() {
list = new ArrayList<>();
}
//自己定义数组长度
public ArrayStack(int capacity) {
list = new ArrayList<>(capacity);
}

//size实现
@Override
public int size() {
return list.size();
}
//判空实现
@Override
public boolean isEmpty() {
return list.isEmpty();
}
//push方法
@Override
public void push(E element) {
list.add(element);
}
//pop方法实现
@Override
public E pop() {
return list.remove(list.size() - 1);
}
//peek实现
@Override
public E peek() {
return list.get(list.size() - 1);
}
//clear方法
@Override
public void clear() {
list.clear();
}
//迭代方法
@Override
public Iterator<E> iterator() {
return list.iterator();
}
//toString方法实现
@Override
public String toString() {
return list.toString();
}
//继承Object equals方法
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if (o instanceof ArrayStack) {
ArrayStack other = (ArrayStack) o;
return this.list.equals(other.list);
}
return false;
}
}

3.基于链表的实现:
基于链表实现一个栈,其基本思路是这样的:通过在链表的表头插入元素的方式来实现push操作;通过删除链表的表头节点的方式来实现pop操作.

public class SinglyNode<K extends Object> {
private K data; // 数据
private SinglyNode<K> next; // 该节点的下个节点

public SinglyNode(K data) {
this.data = data;
}

public SinglyNode(K data, SinglyNode<K> next) {
this.data = data;
this.next = next;
}

public K getData() {
return data;
}

public void setData(K data) {
this.data = data;
}

public SinglyNode<K> getNext() {
return next;
}

public void setNext(SinglyNode<K> next) {
this.next = next;
}

@Override
public String toString() {
return "SinglyNode [data=" + data + "]";
}

}
public class LinkStack<K extends Object> implements Stack<K> {

private SinglyNode<K> headNode;
//是否为满栈
public boolean isFull() {
return false;
}
//是否为空
public boolean isEmpty(){
return headNode == null ? true : false;
}


//入栈
public void push(K data){
if(headNode == null){
headNode = new SinglyNode<K>(data);
}else{
SinglyNode<K> newNode = new SinglyNode<K>(data);
newNode.setNext(headNode);
headNode = newNode;
}
}

//出栈
public K pop(){
if(headNode == null){
throw new EmptyStackException();
}else{
K data = headNode.getData();
headNode = headNode.getNext();
return data;
}
}

//返回栈顶元素
public K top(){
if(headNode == null){
throw new EmptyStackException();
}else{
return headNode.getData();
}
}

//返回栈中元素个数
public int size(){
if(headNode == null){
return 0;
}else{
int length = 0;
SinglyNode<K> currentNode = headNode;
while (currentNode != null) {
length++;
currentNode = currentNode.getNext();
}
return length;
}
}

//清空栈
public void ClearStack(){
headNode = null;
}

//遍历栈从前往后
@Override
public void print() {
SinglyNode<K> tmpNode = headNode;
printFromEnd(tmpNode);
}

//遍历栈从后往前
public void printFromEnd(SinglyNode<K> headNode){
if(headNode != null){
if(headNode.getNext() != null){
printFromEnd(headNode.getNext());
}

System.out.print(headNode.getData() + " ");
}
}

}

 基于数组实现和基于链表实现的比较
(1)基于数组实现的栈:

各个操作都是常数时间开销
每隔一段时间进行的倍增操作的时间开销较大
(2)基于链表实现的栈:
栈规模的增加和减小都很容易
各个操作都是常数时间开销
每个操作都需要使用额外的空间和时间开销来处理指针
4.基于队列实现栈:

import java.util.Iterator;

//队列实现栈
public class QueueToStack {
public static void main(String[] args) {
StackImplByQueue<Integer> stack = new StackImplByQueue<>();
System.out.println(stack);
for (int i = 1; i <= 5; i++) {
stack.push(i); //队列A
}
System.out.println(stack.toString());
System.out.println(stack.pop());
System.out.println(stack); //队列B

}
}

class StackImplByQueue<E> implements Stack<E> { //上方的栈调用
private ArrayQueue<E> queueA;
private ArrayQueue<E> queueB;

public StackImplByQueue() {
queueA = new ArrayQueue<>();
queueB = new ArrayQueue<>();
}

@Override
public int size() {
if (queueA.isEmpty() && queueB.isEmpty()) {
return 0;
} else if (!queueA.isEmpty()) {
return queueA.size();
} else {
return queueB.size();
}
}

@Override
public boolean isEmpty() {
return queueA.isEmpty() && queueB.isEmpty();
}

@Override
public void push(E element) {
if (queueA.isEmpty() && queueB.isEmpty()) {
queueA.offer(element);
} else if (!queueA.isEmpty()) {
queueA.offer(element);
} else {
queueB.offer(element);
}
}

@Override
public E pop() {
if (isEmpty()) {
return null;
}
E ret = null;
if (!queueA.isEmpty()) {
while (queueA.size() != 1) {
queueB.offer(queueA.poll());
}
ret = queueA.poll();
} else {
while (queueB.size() != 1) {
queueA.offer(queueB.poll());
}
ret = queueB.poll();
}
return ret;
}

@Override
public E peek() {
if (isEmpty()) {
return null;
}
E ret = null;
if (!queueA.isEmpty()) {
while (queueA.size() != 1) {
queueB.offer(queueA.poll());
}
ret = queueA.poll();
queueB.offer(ret);
} else {
while (queueB.size() != 1) {
queueA.offer(queueB.poll());
}
ret = queueB.poll();
queueA.offer(ret);
}
return ret;
}

@Override
public void clear() {
queueA.clear();
queueB.clear();
}

@Override
public Iterator<E> iterator() {
if (isEmpty()) {
return queueA.iterator();
} else if (!queueA.isEmpty()) {
return queueA.iterator();
} else {
return queueB.iterator();
}
}
@Override
public String toString() {
if (isEmpty()) {
return "[]";
} else if (!queueA.isEmpty()) {
return queueA.toString();
} else {
return queueB.toString();
}
}

}

Java 集合框架中的 Stack 具有以下特点:
继承自 Vector
有 5 种创建 Stack 的方法
采用数组实现
除了 push(),剩下的方法都是同步的。
双端栈:
双端栈定义:
是指一个线性表的两端当做栈底,分别进行入栈和出栈操作,主要利用了栈:栈底不变,栈顶变化的特征;

 双端栈是线性表的一种,更是栈的一个特殊分类,可用借用动态数组+栈的组合实现。

双端栈实现方法以及基本方法:

import java.util.Iterator;
//双端栈
public class ArrayDoubleEndStack<E> implements Iterable<E> {
//左端栈的栈顶
private int ltop;
//右端栈的栈顶
private int rtop;
//存储元素的容器
private E[] data;
//数组容器的默认容量
private static int DEFAULT_CAPACITY = 10;
//构造
public ArrayDoubleEndStack() {
data = (E[]) new Object[DEFAULT_CAPACITY];
ltop = -1;
rtop = data.length;
}
//左入栈
public void pushLeft(E element) {
if (ltop + 1 == rtop) {
resize(data.length * 2);
}
data[++ltop] = element;
}
//右入栈
public void pushRight(E element) {
if (ltop + 1 == rtop) {
resize(data.length * 2);
}
data[--rtop] = element;
}
//扩容
private void resize(int newLen) {
E[] newData = (E[]) new Object[newLen];
//复制左端栈的元素
for (int i = 0; i <= ltop; i++) {
newData[i] = data[i];
}
//复制右端栈的元素
int index = rtop;
for (int i = newLen - sizeRight(); i < newLen; i++) {
newData[i] = data[index++];
}
rtop = newLen - sizeRight();
data = newData;
}
//左出栈
public E popLeft() {
if (isLeftEmpty()) {
throw new IllegalArgumentException("left stack is null");
}
E ret = data[ltop--];
if (sizeLeft() + sizeRight() <= data.length / 4 && data.length > DEFAULT_CAPACITY) {
resize(data.length / 2);
}
return ret;
}
//右出栈
public E popRight() {
if (isRightEmpty()) {
throw new IllegalArgumentException("right stack is null");
}
E ret = data[rtop++];
if (sizeLeft() + sizeRight() <= data.length / 4 && data.length > DEFAULT_CAPACITY) {
resize(data.length / 2);
}
return ret;
}
//获取左栈顶
public E peekLeft() {
if (isLeftEmpty()) {
throw new IllegalArgumentException("left stack is null");
}
return data[ltop];
}
//获取右栈顶
public E peekRight() {
if (isRightEmpty()) {
throw new IllegalArgumentException("right stack is null");
}
return data[rtop];
}

//判断是否左边为空
public boolean isLeftEmpty() {
return ltop == -1;
}
//判断是否右边为空
public boolean isRightEmpty() {
return rtop == data.length;
}
//通过左指针获取长度
public int sizeLeft() {
return ltop + 1;
}
//通过右指针获取长度
public int sizeRigh() {
return data.length - rtop;
}
//重写toString方法
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('[');
if (isLeftEmpty() && isRightEmpty()) {
sb.append(']');
return sb.toString();
}
//先左边
for (int i = 0; i <= ltop; i++) {
sb.append(data[i]);
if (i == ltop && isRightEmpty()) {
sb.append(']');
return sb.toString();
} else {
sb.append(',');
}
}
//后右边
for (int i = rtop; i < data.length; i++) {
sb.append(data[i]);
if (i == data.length - 1) {
sb.append(']');
} else {
sb.append(',');
}
}
return sb.toString();
}
//迭代器
@Override
public Iterator<E> iterator() {
return new ArrayDoubleEndStackIterator();
}

class ArrayDoubleEndStackIterator implements Iterator<E> {
private ArrayList<E> list;
private Iterator<E> it;
public ArrayDoubleEndStackIterator() {
list = new ArrayList<>();
for (int i = 0; i <= ltop; i++) {
list.add(data[i]);
}
for (int i = rtop; i < data.length; i++) {
list.add(data[i]);
}
it = list.iterator();
}

@Override
public boolean hasNext() {
return it.hasNext();
}

@Override
public E next() {
return it.next();
}
}
}

双端栈的扩容与缩容问题;
双端栈A中有下标0-5的6个元素,对其进行扩容;

对其扩容时候,先考虑左边,从原来双端栈A的左边原封不动直接进入双端栈B,此时与双端栈A的左指针相同,皆为下标2,此时双端栈B的前3个元素不动,下标为【0,ltop】。
其次考虑右边的情况,通过观察,发现index为5的元素对应到11,所以即为newLen-1,而先前的rtop从双端栈A的3变到了双端栈B的9,可以观察到9 = 12 - 3;此时rtop = newLen - size(1)
这时候原先双端栈A的右边完全到了双端栈B的左边,完成了对双端栈A的扩容问题。

关于栈的习题应用:
1.关于题解回文数;
给定一个字符串,判断是否该字符串是否是回文字符串,即"aba" 则为一个满足回文的条件;

public class JudgingPalindrome {
public static void main(String[] args) {
solution01();
System.out.println(solution02());
}
//通过栈的思想
private static void solution01() {
String text = "上海自来水来自海上";
ArrayStack<Character> stack = new ArrayStack<>();
//遍历栈中元素
for (int i = 0; i < text.length(); i++) {
//对于栈中元素是奇是偶进行判断
if (text.length() % 2 == 1 && i == text.length() / 2) {
continue;
}
char c = text.charAt(i);
//栈空进栈,相同时候元素弹出,不同时候进栈,判断最后是否栈为空,为空则回文
if (stack.isEmpty()) {
stack.push(c);
} else {
if (c != stack.peek()) {
stack.push(c);
} else {
stack.pop();
}
}
}
System.out.println(stack.isEmpty());
}
//通过双指针的思想
private static boolean solution02() {
String text = "上海自来水来自海上";
//头指针
int i = 0;
//尾指针
int j = text.length() - 1;
//循环条件,头尾同时进行,首位匹配下一位,不同则返回false;
while (true) {
if (text.charAt(i) == text.charAt(j)) {
i++;
j--;
} else {
return false;
}
//尾指针要小于等于头指针
if (j <= i) {
return true;
}
}
}
}

2.关于括号匹配问题
给定一些列括号‘(‘ ,’)’,‘【‘,’】’等,判断给定的括号串,是否完全匹配。如:“({()()})”;

//括号匹配
import java.util.HashMap;

public class MatchBracket {
public static void main(String[] args) {
solution01();
solution02();
}

//通过栈的思想解决
private static void solution01() {
String str = "{()[[()]]<>{}()<>}()";
ArrayStack<Character> stack = new ArrayStack<>();

//循环该数组下标,栈为空进栈
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (stack.isEmpty()) {
stack.push(c);
} else {
//当前栈顶
char top = stack.peek();
//通过ascll码匹配当满足条件时候,弹栈,即完成匹配
if (top - c == -1 || top - c == -2) {
stack.pop();
} else {
stack.push(c);
}
}
}
//当stack为空时候说明括号完全匹配
System.out.println(stack.isEmpty());
}

//通过HashMap实现
private static void solution02() {
String str = "{()[[()]]<{()>}()";
//给定hashmap的键值对应关系
HashMap<Character,Character> map = new HashMap<>();
map.put('[',']');
map.put('<','>');
map.put('(',')');
map.put('{','}');
ArrayStack<Character> stack = new ArrayStack<>();
//遍历和法1相同
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (stack.isEmpty()) {
stack.push(c);
} else {
char top = stack.peek();
//contains保证键值关系,包含时候,看c是否满足关系,满足时候弹栈,不满足继续入栈
if (map.containsKey(top) && c == map.get(']')) {
stack.pop();
} else {
stack.push(c);
}
}
}
System.out.println(stack.isEmpty());
}
}

3.简单计算器的实现
给定(),+ - */四则运算,做出来一个简单的计算器 如(10+20/2*3)/2+8 = 28
3.1中缀表达式计算器

//中缀表达式计算器
public class InfixCalculator {
public static void main(String[] args) {
String expression = "(10+20/2*3)/2+8";
try {
int result = evaluateExpression(expression);
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Wrong expression :" + expression);
}
}

private static int evaluateExpression(String expression) {
//需要两个辅助栈
ArrayStack<Character> operatorStack = new ArrayStack<>();
ArrayStack<Integer> numberStack = new ArrayStack<>();

//格式化表达式
expression = insertBlanks(expression);
String[] tokens = expression.split(" ");
for (String token : tokens) { //token == tokens[i]
//过滤空串
if (token.length() == 0) {
continue;
//遍历到 + - 号
} else if (token.equals("+") || token.equals("-")) {
while (!operatorStack.isEmpty() && (operatorStack.peek() == '+' || operatorStack.peek() == '-' || operatorStack.peek() == '*' || operatorStack.peek() == '/')) {
//如果之前是别的+ - * / 则需要弹栈 并计算
processAnOperator(numberStack, operatorStack);
}
//如果操作符栈为空 或者 不为空但栈顶为(
operatorStack.push(token.charAt(0));

//遍历到 * / 号
} else if (token.equals("*") || token.equals("/")) {
while (!operatorStack.isEmpty() && (operatorStack.peek() == '*' || operatorStack.peek() == '/')) {
//如果之前是别的* / 则需要弹栈 并计算
processAnOperator(numberStack, operatorStack);
}
//如果操作符栈为空 或者 不为空但栈顶为(
operatorStack.push(token.charAt(0));

//遍历到 (
} else if (token.equals("(")) {
operatorStack.push(token.charAt(0));

//遍历到 )
} else if (token.equals(")")) {
//只要操作符栈的栈顶不是左括号( 就挨个弹栈计算即可
while (operatorStack.peek() != '(') {
processAnOperator(numberStack, operatorStack);
}
//最后 清掉左括号
operatorStack.pop();

//遍历到数字
} else {
numberStack.push(new Integer(token));
}
}

//处理最后面的操作符
while (!operatorStack.isEmpty()) {
processAnOperator(numberStack, operatorStack);
}
return numberStack.pop();
}

//操作符栈弹栈一个元素 数字栈弹栈两个数字 进行计算 并将新的结果进栈到数字栈
private static void processAnOperator(ArrayStack<Integer> numberStack, ArrayStack<Character> operatorStack) {
char op = operatorStack.pop();
int num1 = numberStack.pop();
int num2 = numberStack.pop();
//num2 op num1
if (op == '+') {
numberStack.push(num2 + num1);
} else if (op == '-') {
numberStack.push(num2 - num1);
} else if (op == '*') {
numberStack.push(num2 * num1);
} else {
numberStack.push(num2 / num1);
}
}

//对原表达式进行格式化处理 给所有的非数字字符两边添加空格
private static String insertBlanks(String expression) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/') {
sb.append(' ');
sb.append(c);
sb.append(' ');
} else {
sb.append(c);
}
}
return sb.toString();
}
}

 3.2后缀表达式计算器

//后缀表达式的计算器
public class SuffixCalculator {
public static void main(String[] args) {
String infixExpression = "(10+20/2*3)/2+8";
String suffixExpression = InfixToSuffix.infixToSuffix(infixExpression);
int result = evaluateSuffix(suffixExpression);
System.out.println(result);
}

private static int evaluateSuffix(String expression) {
ArrayStack<Integer> stack = new ArrayStack<>();
String[] tokens = expression.split(" ");
for (String token : tokens) {
if (token.length() == 0) {
continue;
}
if (isNumber(token)) {
stack.push(new Integer(token));
} else {
processAnOperator(stack,token);
}
}
return stack.pop();
}

private static void processAnOperator(ArrayStack<Integer> stack, String token) {
int num1 = stack.pop();
int num2 = stack.pop();
if (token.equals("+")) {
stack.push(num2 + num1);
} else if (token.equals("-")) {
stack.push(num2 - num1);
} else if (token.equals("*")) {
stack.push(num2 * num1);
} else if (token.equals("/")) {
stack.push(num2 / num1);
}
}

private static boolean isNumber(String token) {
return token.matches("\\d+");
}
}

3.3中缀转换后缀

//中缀表达式转后缀表达式
public class InfixToSuffix {
public static void main(String[] args) {
String expression = "(10+20/2*3)/2+8";
expression = infixToSuffix(expression);
System.out.println(expression);
}
private static String infixToSuffix(String expression) {
//操作符的栈
ArrayStack<String> opStack = new ArrayStack<>();
//后缀表达式的线性表
ArrayList<String> suffixList = new ArrayList<>();
//格式化表达式
expression = insertBlanks(expression);
String[] tokens = expression.split(" ");
for (String token : tokens) {
//过滤空串
if (token.length() == 0) {
continue;
}
//判断操作符+ - * /
if (isOperator(token)) {
/*
什么时候操作符进栈?
1.如果栈为空
2.如果栈顶是 (
3.如果栈顶是操作符,且优先级比当前token小
什么时候需要将栈顶操作符出栈?
1.栈顶操作符的优先级 >= 当前token
*/
while (true) {
if (opStack.isEmpty() || opStack.peek().equals("(") || priority(opStack.peek()) < priority(token)) {
opStack.push(token);
break;
}
suffixList.add(opStack.pop());
}
} else if (token.equals("(")) {
opStack.push(token);
} else if (token.equals(")")) {
while (!opStack.peek().equals("(")) {
suffixList.add(opStack.pop());
}
opStack.pop();
} else if (isNumber(token)) {
suffixList.add(token);
} else {
throw new IllegalArgumentException("wrong char :" + expression);
}
}
while (!opStack.isEmpty()) {
suffixList.add(opStack.pop());
}
//将数字元素和操作符元素进行拼接
StringBuilder sb = new StringBuilder();
for (int i = 0; i < suffixList.size(); i++) {
sb.append(suffixList.get(i));
sb.append(' ');
}
return sb.toString();
}

private static int priority(String token) {
if (token.equals("+") || token.equals("-")) {
return 0;
}
if (token.equals("*") || token.equals("/")) {
return 1;
}
return -1;
}

private static boolean isNumber(String token) {
return token.matches("\\d+");
}

private static boolean isOperator(String token) {
return token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/");
}

//对原表达式进行格式化处理 给所有的非数字字符两边添加空格
private static String insertBlanks(String expression) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/') {
sb.append(' ');
sb.append(c);
sb.append(' ');
} else {
sb.append(c);
}
}
return sb.toString();
}
}


C 1条回复 评论
知乎

是道好题,会了这道就能举一反三

发表于 2024-08-15 23:00:00
0 0