1. 栈
1.1 概念
栈:
- 一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。
- 进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。
- 栈中的数据元素遵守后进先出的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈,出数据在栈顶。
1.2 栈的使用
方法 | 功能 |
Stack() | 构造一个空的栈 |
E push(E e) | 将e入栈,并返回e |
E pop() | 将栈顶元素出栈并返回 |
E peek() | 获取栈顶元素 |
int size() | 获取栈中有效元素个数 |
boolean empty() | 检测栈是否为空 |
1.3 栈的模拟实现
import java.util.Arrays;
public class MyStack {
private int[] elem;//底层是一个数组
private int usedSize;
public MyStack(){
this.elem = new int[5];
}
public void push(int val){
if (isFull()){
elem = Arrays.copyOf(elem,2*elem.length);
}
elem[usedSize] = val;
usedSize++;
}
public boolean isFull(){
return usedSize == elem.length;
}
// 将元素弹出,删除元素
public int pop(){
//1.判断栈不为空
if (empty()){
throw new StackEmptyException("栈为空");
}
//2.开始删除
return elem[--usedSize];
}
public boolean empty(){
return usedSize == 0;
}
//只获取栈顶元素不删除
public int peek(){
//1.判断栈不为空
if (empty()){
throw new StackEmptyException("栈为空");
}
//2.开始删除
return elem[usedSize-1];
}
}
public class StackEmptyException extends RuntimeException{
public StackEmptyException(){
}
public StackEmptyException(String message) {
super(message);
}
}
下面思考一个问题,栈的底层是一个数组,那么栈可以用链表实现嘛?
答:可以
用数组实现栈时,入栈和出栈的时间复杂度是O(1),用链表实现,也要保证出栈和入栈的时间复杂度.
首先看单链表:
下面看双向链表:
所以Linkedlist也经常被当作栈使用.
1.4 栈的应用场景
1.4.1 改变元素的序列
1.4.2 将递归化为循环(逆序打印链表)
将每个节点压栈,然后pop( )
// 递归方式
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 + " ");
}
1.4.3 括号匹配
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;
}else{
char tmp = stack.peek();
if(ch == ')' && tmp == '(' ||ch == '}' && tmp == '{' ||ch == ']' && tmp == '[' ){
//这次括号匹配成功
stack.pop();
}else{
return false;
}
}
}
}
if(!stack.empty()){
return false;
}
return true;
}
1.4.4 逆波兰表达式求值
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for(String s:tokens){
if(!isOpera(s)){
//数字:放入栈当中
stack.push(Integer.parseInt(s));
}else{
//弹出栈顶的两个元素
int num2 = stack.pop();
int num1 = stack.pop();
switch(s){
case"+":
stack.push(num1+num2);
break;
case"-":
stack.push(num1-num2);
break;
case"*":
stack.push(num1*num2);
break;
case"/":
stack.push(num1/num2);
break;
}
}
}
return stack.pop();
}
//是不是运算符
public boolean isOpera(String x){
if(x.equals("+") || x.equals("-")||x.equals("*")||x.equals("/") ){
return true;
}
return false;
}
1.4.5 出栈入栈次序匹配
public boolean IsPopOrder (int[] pushA, int[] popA) {
Stack<Integer> stack = new Stack<>();
int j = 0;//遍历popA
for (int i = 0; i < pushA.length;i++){
stack.push(pushA[i]);
while (!stack.empty() && j < popA.length && stack.peek() == popA[j]){
stack.pop();
j++;
}
}
return stack.empty();
}
1.4.6 最小栈
import java.util.Stack;
public class MinStack {
private Stack<Integer> stack;
private Stack<Integer> minstack;
public MinStack() {
stack = new Stack<>();
minstack = new Stack<>();
}
public void push(int val) {
stack.push(val);
//第一次在最小栈当中存储元素
if (minstack.empty()){
minstack.push(val);
}else {
if (val <= minstack.peek()){
minstack.push(val);
}
}
}
public void pop() {
//栈为空,不能弹出
if (stack.empty()){
return;
}
int val = stack.pop();
if (val == minstack.peek()){
minstack.pop();
}
}
//获取栈顶元素,和最小栈没有关系
public int top() {
if (stack.empty()){
return -1;
}
return stack.peek();
}
public int getMin() {
return minstack.peek();
}
public static void main(String[] args) {
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(1);
int x1 = minStack.getMin();
int x2 = minStack.top();
minStack.pop();
int x3 = minStack.getMin();
}
}
1.5 概念区分(简单区分)
栈、虚拟机栈、栈帧有什么区别呢?
- 栈:一种特殊的线性表,遵守后进先出的原则。
- 虚拟机栈:具有特殊作用的一块内存空间。
- 栈帧:方法调用时,在虚拟机栈上开辟的内存。