1.栈的基本认识
栈的结构如图所示:栈本身就是一个数组
我们发现,栈的结构和顺便表,链表有所差异,主要是因为栈对元素的存取有所特殊要求.
🧐
1.栈只能从一段存取数据,另一端是封闭的
2.在栈中,存取数据都要遵守先进后出的原则,也就是最先进栈的最后出栈,最后进栈的最先出先出栈.
1.1 栈的定义
栈是一种只能从一段存取数据且遵循"先进后出"原则的线性存储结构.
栈的开口端被称为栈顶,闭口端称为栈底.
在了解了栈的基本结构之后,那么栈是如何实现元素的增加和删除呢?
😃删除一个元素:也就是把栈顶元素移开,让该元素下面的元素作为新的栈顶. 也叫做入栈
😃增加一个元素:让新增的元素变为新的栈顶.也叫做出栈
2.栈的模拟实现
实现代码:
public class MyStack {
public int[] elem;
public int usedSize;//默认0
MyStack(){//栈的初始化
elem=new int[]{1,2,3,4};
usedSize=elem.length;
}
//判断是否为空
public boolean isEmpty(){
if(usedSize==0){
return true;
}
return false;
}
//判断栈是否满了
public boolean ifFull(){
return usedSize==elem.length;
}
//入栈
public void push(int val){
if(ifFull()){
elem= Arrays.copyOf(elem,2*elem.length);//2倍扩容
}
elem[usedSize]=val;
usedSize++;
}
//出栈
public int pop(){
if(isEmpty()){
System.out.println("栈为空,无法出栈");
return -1;
}
int val=elem[usedSize-1];
usedSize--;
return val;
}
//获取栈顶元素,只是获取,并不出栈
public int peek(){
if(isEmpty()){
System.out.println("栈为空,不能获取");
return -999;
}
return elem[usedSize-1];
}
//获取栈中元素个数
public int size(){
return usedSize;
}
//打印栈中所有元素
public void printMyStack(){
for(int i=0;i<usedSize;i++){
System.out.print(elem[i]);
}
System.out.println();
}
}
测试代码:
public class Test1 {
public static void main(String[] args) {
MyStack myStack=new MyStack();
myStack.printMyStack();
System.out.println("==================");
myStack.pop();
myStack.printMyStack();
System.out.println("==================");
myStack.push(8);
myStack.printMyStack();
System.out.println("==================");
System.out.println("栈中此时的元素个数是"+myStack.size());
}
}
结果:
1234
==================
123
==================
1238
==================
栈中此时的元素个数是4
3. 相关练习题
😎有效的括号 OJ链接
大体思路:
1.遍历字符串,遇到左括号则入栈,遇到右括号,则将两个括号进行匹配判断,如果匹配,则出栈,然后继续遍历,如果不匹配,则字符串无效,直接返回.
💙当字符串遍历完,栈正好也为空,则有效 💙当字符串遍历完,栈不为空,说明左括号多,无效
💙如果字符串还没遍历完,栈就为空,说明右括号多,无效
代码:
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
String s=sc.nextLine();
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;
}
char tmp=stack.peek();//就将左括号出栈并与这个右括号比较,看是否匹配
//如果不匹配,那么这个字符串无效
if(ch==')'&&tmp=='('){
stack.pop();
} else if (ch=='}'&&tmp=='{') {
stack.pop();
} else if (ch==']'&&tmp=='[') {
stack.pop();
}else {
System.out.println("不是有效字符串");
return;
}
//如果匹配,那么继续遍历字符串,重复上述步骤
}
}
if(!stack.empty()){//如果字符串遍历完,栈不为空,说明左括号更多,这些括号多于出来,无法匹配,说明是无效的
System.out.println("不是有效字符串");
return;
}
System.out.println("是有效字符串");
}
3.2 补充:递归打印链表
public void disPlay(ListNode head){
if(head==null){
return;
}
if(head.next==null){
System.out.print(head.val);
}
disPlay(head.next);
System.out.print(head.val+" ");
}
3.3 利用栈打印链表
public void disPlay(){
Stack<ListNode> stack=new Stack<>();
ListNode cur=head;
while(cur!=null){
stack.push(cur);
cur=cur.next;
}
while(!stack.empty()){
ListNode top=stack.pop();
System.out.print(top.val+" ");
}
}
😎逆波兰表达式 OJ链接
大体思路:
1.什么是逆波兰表达式?
逆波兰表达式,也就是没有括号,运算符总是放在和它相关的操作数之后因此逆波兰表达式也叫做后缀表达式.
举个例子🍔:将中缀表达式,转换为后缀表达式
像中缀表达式"
1+((2+3)*4)-5
"
转换为后缀表达式,就是"
123+4*5-
"
2.遍历字符串数组,若遇到数字,则将数字入栈,如果遇到运算符,则出栈(出两个元素),将第一个出栈的元素放在运算符右侧,第二个出栈的元素放在运算符左侧.(这个顺序很重要).然后将运算得到的结果入栈.整个数组遍历结束后,栈内只有一个元素,也就是逆波兰表达式的值.
代码:
//逆波兰表达式求值
//遍历数组,只要是数字就入栈,遇到运算符,就出两个栈里的元素,并且把先出的放在运算符右侧,后出的放在运算符左侧,将计算结果再次入栈
public static void main(String[] args) {
//给定一个字符串数组,由这个字符数组得到一个表达式,最后得出表达式的值
String[] tokens={"2","1","+","3","*"};
//给定一个栈
Stack<Integer> stack=new Stack<>();
//遍历字符串数组
for(int i=0;i<tokens.length;i++){
String s=tokens[i];
if(tokens[i].equals("+")||tokens[i].equals("-")||tokens[i].equals("*")||tokens[i].equals("/")){//遇到运算符就出栈
int right=stack.pop();
int left=stack.pop();
switch (s){
case "+":{
stack.push(left+right);
break;//注意break
}
case "-":{
stack.push(left-right);
break;
}
case "*":{
stack.push(left*right);
break;
}
case "/":{
stack.push(left/right);
break;
}
}
}
else {
stack.push(Integer.parseInt(tokens[i]));//转成整数
}
}
System.out.println(stack.peek());//栈中剩最后的结果
}
😎栈的压入弹出序列 OJ链接
代码:
public static void main(String[] args) {
int[] push= new int[]{1, 2, 3, 4, 5};
int[] pop=new int[]{4,5,3,2,1};
int j=0;
Stack<Integer> stack=new Stack<>();
for(int i=0;i<push.length;i++){
stack.push(push[i]);
while(j<pop.length&&!stack.empty()&&stack.peek().equals(pop[j])){
stack.pop();
j++;
}
}
boolean ret=stack.empty();
System.out.println(ret);
}
😎最小栈 OJ链接
😉这一题的关键之处在于"要在常数时间内找到栈中此时最小的元素".解决方法就是,借助一个辅助栈,让一个栈中的最小元素始终对应辅助栈的栈顶元素.
🤨我们举个例子说明一下:
假设我们要让元素3,4,1,6,2依次入栈,我们定义两个栈,一个正常出入栈,另一个辅助栈用来存放正常栈的不同时刻对应的最小的元素.
元素3,4,1,6,2都要依次入stack栈,而minSatck不会让所有的元素入栈,而是要让不同时刻对应的最小的元素入minStack栈.当两个栈都为空时,3这个元素两个栈都入,再要入4这个元素的时候,要用4去和minStack栈里的元素去比较,如果这个数比minStack的栈顶元素要小于或者等于,那么就两个栈都入,如果这个数大于minStack的栈顶元素,那么这个数就只入stack这个栈.
代码:
public class MinStack2 {
Stack<Integer> stack,minStack;//minStack就是辅助栈
public MinStack2() {
stack=new Stack<>();
minStack=new Stack<>();
}
public void push(int x) {
stack.push(x);
if(minStack.empty()||minStack.peek()>=x){
minStack.push(x);
}
}
public void pop() {
if(stack.pop().equals(minStack.peek())){
minStack.pop();
}
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}