目录
1.有关概念
2.基本方法和使用场景
栈的结构就像手枪,push方法相当于装子弹,先装的子弹后打出,最后装的子弹先打出。pop方法就是射击,将弹夹最上面的子弹射出。在手枪射击的过程中,我们也可以随时补充子弹,同时子弹的射出顺序也就发生了改变,这就说明栈的重要应用场景就是改变数据序列的顺序。
2.1.方法
方法 | 功能 |
Stack()
|
构造一个空的栈
|
E push(E e)
|
将
e
入栈,并返回
e
|
E pop()
|
将栈顶元素出栈并返回
|
E peek()
|
获取栈顶元素
|
int size()
|
获取栈中有效元素个数
|
boolean empty()
|
检测栈是否为空
|
2.2.使用
public static void main(String[] args) {
Stack<Integer> s = new Stack();
s.push(1);
s.push(2);
s.push(3);
s.push(4);
System.out.println(s.size()); // 获取栈中有效元素个数---> 4
System.out.println(s.peek()); // 获取栈顶元素---> 4
s.pop(); // 4出栈,栈中剩余1 2 3,栈顶元素为3
System.out.println(s.pop()); // 3出栈,栈中剩余1 2 栈顶元素为3
if(s.empty()){
System.out.println("栈空");
}else{
System.out.println(s.size());
}
}
2.3.应用场景
2.3.1.改变元素的序列
2.3.2.逆序打印链表
// 递归方式
void printList(Node head){
if(null != head){
printList(head.next);
System.out.print(head.val + " ");
}
}
// 循环方式
void printList(Node head){
if(null == head){
return;
}
Stack<Node> s = new Stack<>(); // 将链表中的结点保存在栈中
Node cur = head;
while(null != cur){
s.push(cur);
cur = cur.next;
}
// 将栈中的元素出栈
while(!s.empty()){
System.out.print(s.pop().val + " ");
}
}
2.3.3.oj题括号匹配
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for(int i = 0;i<s.length();i++){
char ch = s.charAt(i);
if(ch == '('||ch == '['||ch == '{'){
//左括号入栈
stack.push(ch);
}else{
//右括号多
if(stack.empty()){
return false;
}
//右括号取出栈顶元素匹配
char c = stack.peek();
//括号不匹配
if((c == '('&&ch==')')||(c == '['&&ch==']')||(c == '{'&&ch=='}')) {
stack.pop();
}else{
return false;
}
}
}
//遍历结束栈中还有元素说明左括号多
if(!stack.empty()){
return false;
}
return true;
}
图解:
2.3.4.逆波兰式求值
根据逆波兰表示法,求表达式的值。有效的算符包括 +
、-
、*
、/
。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for(String x:tokens){
//如果是数字字符串
if(!isOperation(x)){
//将数字字符串转为数字压入栈
stack.push(Integer.parseInt(x));
}else{
//如果是运算符字符串,将顶两个数据出栈运算
int num1 = stack.pop();
int num2 = stack.pop();
switch (x){
case "+":
stack.push(num2+num1);
break;
case "-":
stack.push(num2-num1);
break;
case "*":
stack.push(num2*num1);
break;
case "/":
stack.push(num2/num1);
break;
}
}
}
return stack.pop();
}
//判断是否是运算符
private boolean isOperation(String s) {
if(s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/")) {
return true;
}
return false;
}
图解:
2.3.5.栈的弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
public boolean IsPopOrder(int [] pushA,int [] popA) {
Stack<Integer> stack = new Stack<>();
int j = 0;
for(int i = 0;i<pushA.length;i++){
stack.push(pushA[i]);
while(j<popA.length&&!stack.empty()&&stack.peek() == popA[j]){
stack.pop();
j++;
}
}
return stack.empty();
}
解析:pushA数组是压栈顺序,popA数组是需要判断的弹出序列,没有遇到出栈数据前,将数据依次压入stack栈中,当stack栈顶数据和需要出栈的数据相同时,将stack栈的栈顶元素出栈,遍历完pushA数组,如果stack栈不为空,说明不为弹出序列;栈为空,说明符合栈的压栈和出栈特点。
3.栈的简单实现(数组实现)
import java.util.Arrays;
public class MyStack {
public int[] elem;
public int usedSize;
//记录当前元素个数,还是放元素时数组的下标
public MyStack() {
this.elem = new int[10];
}
public void push(int val){
if(isFull()){
this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
}
this.elem[this.usedSize] = val;
this.usedSize++;
}
//判断是否为满
public boolean isFull(){
return this.usedSize == this.elem.length;
}
//出栈
public int pop() {
if(empty()){
throw new RuntimeException("栈为空");
}
int val = this.elem[this.usedSize-1];
this.usedSize--;
return val;
}
//获取栈顶元素
public int peek() {
if(empty()){
throw new RuntimeException("栈为空");
}
return this.elem[this.usedSize-1];
}
//判断是否为空
public boolean empty() {
return this.usedSize == 0;
}
//栈元素个数
public int size(){
return this.usedSize;
}
}
4.有关栈的概念区分
栈:一种数据结构,是一种线性表,数据遵循先进后出。
栈帧:方法的运行空间,每个方法都有属于自己的栈帧,每个栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向运行时常量的引用( the run-time constant pool)、方法返回地址(Return Address)和附加信息。
虚拟机栈:JVM虚拟机用来储存栈帧的数据结构,虚拟机栈中的数据元素就是虚拟机运行时开辟的栈帧元素。