栈和队列(详解)

发布时间:2024年01月16日

【本节目标】
1. 栈的概念及使用
2. 队列的概念及使用
3. 相关OJ题

1.栈(Stack)

1.1 概念

栈:一种特殊的线性表,其只允许在固定一端进行插入和删除元素的操作,进行数据插入和删除操作的一端称为栈顶,另一端称为栈底,栈中的数据遵守后进后出的原则.

栈在生活中的例子:

1.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
}

1.3 栈的模拟实现

从上图中可以看到,Stack继承了Vector,Vector和ArrayList类似,都是动态的顺序表,不同的是Vector是线程安全的。

import java.util.Arrays;

public class Mystack implements IStack{
    int[] elem;
private final int chushi=10;
    public Mystack(int[] elem) {
        this.elem = new int[chushi];
    }
    int usesize = 0;

    @Override
    public int push(int e) {
        if(full()){
            elem= Arrays.copyOf(elem,elem.length*2);
        }
        elem[usesize]=e;
         usesize++;
        return e;
    }
    @Override
    public boolean full(){
       if(usesize == elem.length){
           return true;
       }
       else {
           return false;
       }
    }

    @Override
    public int pop() {
        if(full()){
throw new EmpyExcetion("栈空了");
        }
        int old = elem[usesize-1];
        usesize--;
        return old;
    }

    @Override
    public int peek() {
       if(full()){
           throw new EmpyExcetion("栈空了");
       }
       return elem[usesize-1];
    }

    @Override
    public int size() {
        return 0;
    }

    @Override
    public boolean empty() {
        return usesize==0;
    }
    public class EmpyExcetion extends RuntimeException{
        public EmpyExcetion(String a) {
            super(a);
        }
    }

}

1.4 栈的应用场景

1.改变元素的序列

1. 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()
?A: 1,4,3,2 ?B: 2,3,4,1 ?C: 3,1,4,2 ?D: 3,4,2,1

2.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )。
A: 12345ABCDE ?B: EDCBA54321 ?C: ABCDE12345 ?D: 54321EDCBA

2.将递归转化成循环

比如逆序打印链表:

//递归方式
void printList(Node head){
??if(null != head){
????printList(head.next);
????System.out.print(head.val + " ");
?}
}

//循环方式
??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.括号匹配

20. 有效的括号 - 力扣(LeetCode)

class Solution {
    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 ch2 = stack.peek();
        if((ch2=='{'&&ch=='}') || (ch2=='('&&ch==')') || (ch2=='['&&ch==']')){
            stack.pop();
        }
        else{
return false;
        }
    }
}
if(!stack.empty()){
    return false;
}
return true;
    }
}

2.逆波兰表达式求值

150. 逆波兰表达式求值 - 力扣(LeetCode)

class Solution {
    public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for(String x:tokens){
    if(!Opertion(x)){
        stack.push(Integer.parseInt(x));
    }
    else{
        int a = stack.pop();
    int b = stack.pop();
switch(x){
case "+":
stack.push(a+b);
break;
case "-":
stack.push(b-a);
break;
case "*":
stack.push(a*b);
break;
case "/":
stack.push(b/a);
break;
}
    }
}
return stack.pop();
    }

    public boolean Opertion(String x){
if(x.equals("*") || x.equals("/") || x.equals("-") || x.equals("+")){
    return  true;
}
else{
    return false;
}
    }
}

5.验证栈的序列

946. 验证栈序列 - 力扣(LeetCode)

class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> pushh =  new Stack<>();
int j =0;
for(int i=0; i<pushed.length; i++){
    pushh.push(pushed[i]);
    while(j<pushed.length&& !pushh.empty()&&popped[j]==pushh.peek() ){
pushh.pop();
j++;
    }
}
return pushh.empty();
    }
}

6. 最小栈

155. 最小栈 - 力扣(LeetCode)

class MinStack {
Stack<Integer> stack;
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{
    int tem = minstack.peek();
    if(val<=tem){
        minstack.push(val);
    }
}
    }
    
    public void pop() {
int tem = stack.pop();
if(!minstack.empty()){
if(tem==minstack.peek()){
    minstack.pop();
    }
}
    }
    
    public int top() { 
        return stack.peek();
    }
    
    public int getMin() {
if(!minstack.empty()){
    return minstack.peek();
}
return -1;
    }
}

1.5 区分概念

栈,虚拟基栈,栈帧有什么区别?

栈是一种数据结构

虚拟基栈是JVM划分的一块内存

栈帧是调用这个方法的时候会在虚拟机中开辟一块内存

2.队列(Queue)

2.1 概念

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行除操作的一端称为队头(Head/Front).

2.2 队列的使用

在java中,Queue是个接口,底层是通过链表实现的.

操作方法:

注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。

public static void main(String[] args) {
??Queue<Integer> q = new LinkedList<>();
??q.offer(1);
??q.offer(2);
??q.offer(3);
??q.offer(4);
??q.offer(5); ?????????// 从队尾入队列
??System.out.println(q.size());
??System.out.println(q.peek()); ?// 获取队头元素
?
??q.poll();
??System.out.println(q.poll()); ?// 从队头出队列,并将删除的元素返回
?
??if(q.isEmpty()){
????System.out.println("队列空");
?}else{
????System.out.println(q.size());
?}

2.3 模拟实现队列

队列中既然可以存储元素,那底层肯定要有能够保存元素的空间,通过前面线性表的学习了解到常见的空间类型有两种:顺序结构 和 链式结构。

  1. 插入和删除操作效率高:LinkedList实现的队列在插入和删除操作时效率更高,因为它的底层数据结构是链表,插入和删除元素时只需要改变指针指向,不需要移动元素。

  2. 内存占用更灵活:LinkedList实现的队列在添加或删除元素时,不需要像ArrayList一样重新分配内存空间,因此内存占用更加灵活。

  3. 支持更多操作:LinkedList实现的队列支持更多的操作,如在任意位置插入或删除元素,而ArrayList实现的队列只能在末尾进行插入和删除操作。

  4. 对于大量的插入和删除操作,LinkedList实现的队列性能更好。但是对于随机访问的操作,ArrayList实现的队列性能更好。

public class Queue {
??// 双向链表节点
??public static class ListNode{
????ListNode next;
????ListNode prev;
????int value;
????ListNode(int value){
??????this.value = value;
???}
?}
??ListNode first; ?// 队头
??ListNode last; ??// 队尾
??int size = 0;
??// 入队列---向双向链表位置插入新节点
??public void offer(int e){
????ListNode newNode = new ListNode(e);
????if(first == null){
??????first = newNode;
??????// last = newNode;
???}else{
??????last.next = newNode;
??????newNode.prev = last;
??????// last = newNode;
???}
????last = newNode;
????size++;
?}
??// 出队列---将双向链表第一个节点删除掉
??public int poll(){
????// 1. 队列为空
????// 2. 队列中只有一个元素----链表中只有一个节点---直接删除
????// 3. 队列中有多个元素---链表中有多个节点----将第一个节点删除
????int value = 0;
????if(first == null){
??????return null;
???}else if(first == last){
??????last = null;
??????first = null;
???}else{
??????value = first.value;
??????first = first.next;
??????first.prev.next = null;
??????first.prev = null;
???}
????--size;
????return value;
?}
??// 获取队头元素---获取链表中第一个节点的值域
??public int peek(){
????if(first == null){
??????return null;
???}
????return first.value;
?}
??public int size() {
????return size;
?}
??public boolean isEmpty(){
????return first == null;
?}
}

2.4 双端队列

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。

Deque是一个接口,使用时必须创建LinkedList的对象.

在实际工程中,使用Deque接口是比较多的,栈和队列均可以使用该接口。

Deque<Integer> d1 = new ArrayDeque<>();
Deque<Integer> d2 = new LinkedList<>();

习题:用队列模拟栈

225. 用队列实现栈 - 力扣(LeetCode)

class MyStack {
Queue<Integer> qu1;
Queue<Integer> qu2;
    public MyStack() {
qu1 = new LinkedList<>();
qu2 = new LinkedList<>();
    }
    
    public void push(int x) {
if(!qu1.isEmpty()){
qu1.offer(x);
}
else if(!qu2.isEmpty()){
qu2.offer(x);
}
else{
    qu1.offer(x);
}
    }
    
    public int pop() {
if(empty()){
    return -1;
}
if(!qu1.isEmpty()){
    int size = qu1.size();
    for(int i = 0;i<size-1;i++){
int x = qu1.poll();
qu2.offer(x);
    }
    return qu1.poll();
}
else{
int size = qu2.size();
for(int i= 0; i<size-1 ; i++){
    int x = qu2.poll();
    qu1.offer(x);
}
return qu2.poll();
}
    }
    //
    public int top() {
if(empty()){
    return -1;
}
if(!qu1.isEmpty()){
    int size = qu1.size();
    int x =-1;
    for(int i = 0;i<size ;i++){
x = qu1.poll();
qu2.offer(x);
    }
    return x;
}
else{
int size = qu2.size();
int x = -1;
for(int i= 0; i<size ; i++){
    x = qu2.poll();
    qu1.offer(x);
}
return x;
}
    }
    //
    public boolean empty() {
return qu1.isEmpty() && qu2.isEmpty();
    }
}

文章来源:https://blog.csdn.net/cnjdd/article/details/135420345
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。