[爪哇之旅] 栈与队列

读书好,读好书,好读书

 

本文将栈与队列的相关知识点基本都覆盖了,更有各种图解及应用帮助大家理解什么是栈和队列,如何去使用栈和队列,并有多个题型来帮助大家巩固所学,文末更是有相关的面试题,并附上了十分详细的图解题解,希望对大家有所帮助!!!

 


目录

 1. 栈(Stack)

1.1 概念

1.2 栈的使用 

 1.3 栈的模拟实现

 1.4 栈的应用场景

  1. 改变元素的序列

 2. 将递归转化为循环

3. 括号匹配

4. 逆波兰表达式求值

5. 出栈入栈次序匹配

6.最小栈

1.5 概念区分

2. 队列(Queue) 

 2.1 概念

2.2 队列的使用 

2.3 队列模拟实现

 2.4 循环队列

3. 双端队列 (Deque)

4. 面试题

4.1 用队列实现栈 

 代码实现 

4.2 用栈实现队列

 代码实现


 1. 栈(Stack)

1.1 概念

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
出栈:栈的删除操作叫做出栈。出数据在栈顶。

栈在生活中的例子: 

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
    if(s.empty()){
        System.out.println("栈空");
    }else{
        System.out.println(s.size());
    }
}

 1.3 栈的模拟实现

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

public class MyStack {
    int[] array;
    int size;

    public MyStack() {
        array = new int[3];
    }

    public int push(int e) {
        ensureCapacity();
        array[size++] = e;
        return e;
    }

    public int pop() {
        int e = peek();
        size--;
        return e;
    }

    public int peek() {
        if (empty()) {
            throw new RuntimeException("栈为空,无法获取栈顶元素");
        }
        return array[size - 1];
    }

    public int size() {
        return size;
    }

    public boolean empty() {
        return 0 == size;
    }

    private void ensureCapacity() {
        if (size == array.length) {
            array = Arrays.copyOf(array, size * 2);
        }
    }
}

 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

A.1进栈后就出栈,然后2,3,4依次进栈然后依次出栈

B.1,2进栈,然后2出栈,3进栈然后出栈,4进栈然后出栈,最后1出栈

C.3要最先出栈,所以1,2,3要先依次进栈,3出栈后栈顶元素为2,所以1无法第二个出栈

D.1,2,3依次进栈,然后3出栈,4进栈然后出栈,接下来2出栈,1出栈


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

因为是依次入栈,最先进栈的会在栈的底部,所以栈中应该是: 栈顶  EDCBA54321  栈底   ,又因为出栈时只能从栈顶元素开始出栈,所以出栈顺序为EDCBA54321

 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 + " ");
    }
}

3. 括号匹配

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        //1.遍历字符串
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            //2.判断是否是左括号
            if (ch == '(' || ch == '[' || ch == '{') {
                stack.push(ch);
            } else {
                //3.右括号
                //3.1栈空
                if (stack.isEmpty()) {
                    return false;
                }
                //3.2栈不为空
                char ch2 = stack.peek();//左括号
                if (ch == ')' && ch2 == '(' || ch == ']' &&
 ch2 == '[' || ch == '}' && ch2 == '{') {
                    stack.pop();
                } else {
                    return false;
                }
            }
        }
        if (!stack.isEmpty()) {
            return false;
        }
        return true;
    }
}

4. 逆波兰表达式求值

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for(int i = 0; i < tokens.length; i++) {
            String str = tokens[i];
            if(!noNum(str)) {
                //是数字,说明不是字符
                int val = Integer.valueOf(str);
                stack.push(val);
            }else{
                int num2 = stack.pop();
                int num1 = stack.pop();
                //字符
                switch(str) {
                    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();
    }

    private static boolean noNum(String str) {
        if(str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/")){
            return true;
        }
        return false;
    }
}

5. 出栈入栈次序匹配

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pushV int整型一维数组 
     * @param popV int整型一维数组 
     * @return bool布尔型
     */
    public boolean IsPopOrder (int[] pushV, int[] popV) {
        // write code here
        int n = popV.length;
        //辅助栈
        Stack<Integer> s = new Stack<>();
        //入栈的下标
        int j = 0;
        //遍历出栈的数组
        for(int i = 0; i < popV.length; i++) {
            //遍历入栈的数组
            //1.当栈为空且栈顶元素不等于当前出栈数组元素时
            while(j < n && (s.isEmpty() || s.peek() != popV[i])) {
                s.push(pushV[j]);
                j++;
            }
            //2.栈顶元素等于出栈数组元素时
            if(s.peek() == popV[i]) {
                s.pop();
            } else{
                return false;
            }
        }
        return true;
    }
}

6.最小栈

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 peekNum = minStack.peek();
            if(peekNum >= val) {
                minStack.push(val);
            }
        }
    }
    
    public void pop() {
        int popNum = stack.pop();
        if(popNum == minStack.peek()){
            minStack.pop();
        }
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        return minStack.peek();
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

1.5 概念区分

栈、虚拟机栈、栈帧有什么区别呢? 

简单来说,栈是一种数据结构,虚拟机栈是JVM(java virtual machine)的内存,栈帧是调用方法时开辟的内存。 

2. 队列(Queue) 

 2.1 概念

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

 

2.2 队列的使用 

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

 

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

2.3 队列模拟实现

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

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 循环队列

实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列通常使用数组实现。 

数组下标循环的小技巧 

 1. 下标最后再往后(offset 小于 array.length): index = (index + offset) % array.length

2. 下标最前再往前(offset 小于 array.length): index = (index + array.length - offset) % array.length

如何区分空与满 

1. 通过添加 size 属性记录
2. 保留一个位置
3. 使用标记

3. 双端队列 (Deque)

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

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

 

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

Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现

4. 面试题

4.1 用队列实现栈 

要用队列去实现一个栈,用一个队列可不可行?注意:这里的队列指的是普通队列,不是双端队列,我们可以试想一下,假设往一个队列中存12 和 23两个数据,队列“先进先出”,12先进队列,那么12就会先出队列,而栈“先进后出”,12先进栈中,23却是先出栈的,显然一个队列无法实现栈,如下图所示:

所以这里我们采用两个队列来实现栈

1.当两个队列是空时,把数据放入第一个队列中,如下图:

 2. 我们要出栈便可以把12和23放到qu2,剩下的qu1的34便是我们要出的元素,如下图

3.如果我们需要往栈中放入元素时候,也就是再次“入栈”时,把数据放到不为空的队列,原因很简单,就是为了让其中一个队列为空,方便让我们入栈和出栈的操作,如下图插入元素45

4.“出栈”时,出不为空的队列,出size-1个元素,剩下的便是要出栈的元素

具体操作步骤如下:

  1. 当两个队列是空时,把数据放入第一个队列中
  2. 再次“入栈”时,数据放到不为空的队列
  3. “出栈”时,出不为空的队列,出size-1个元素,剩下的便是要出栈的元素

 代码实现 

class MyStack {
    private Queue<Integer> qu1;
    private Queue<Integer> qu2;

    public MyStack() {
        qu1 = new LinkedList<>();
        qu2 = new LinkedList<>();
    }
    
    public void push(int x) {
        if(empty()) {
            qu1.offer(x);
            return;
        }
        if(!qu1.isEmpty()) {
            qu1.offer(x);
        }else{
            qu2.offer(x);
        }
    }
    
    public int pop() {
        if(empty()){
            return -1;
        }

        if(!qu1.isEmpty()) {
            int size = qu1.size();
            for(int i = 0; i < size - 1; i++) {
                qu2.offer(qu1.poll());
            }
            return qu1.poll();
        }else{
            int size = qu2.size();
            for(int i = 0; i < size - 1; i++) {
                qu1.offer(qu2.poll());
            }
            return qu2.poll();
        }
    }
    
    public int top() {
        if(empty()){
            return -1;
        }

        if(!qu1.isEmpty()) {
            int size = qu1.size();
            int tmp = -1;
            for(int i = 0; i < size; i++) {
                tmp = qu1.poll();
                qu2.offer(tmp);
            }
            return tmp;
        }else{
            int size = qu2.size();
            int tmp = -1;
            for(int i = 0; i < size; i++) {
                tmp = qu2.poll();
                qu1.offer(tmp);
            }
            return tmp;
        }
    }
    
    public boolean empty() {
        return qu1.isEmpty() && qu2.isEmpty();
    }
}

4.2 用栈实现队列

跟上题一样,一个队列也无法实现队列,如下图:当队列要出12的时候,栈只能出34

跟上题一样,我们需要两个栈来实现

 

当我们要出元素时候,只需要将s1中的元素都放到s2去,然后出s2的栈顶元素即可

 

 当我们要添元素时候,把元素放到第一个栈中

 

 具体思路:

  1. "入队":把数据放到第一个栈中;
  2. “出队”:出s2的栈顶元素,如果s2为空,把s1中的元素都放到s2中
  3. 当两个栈都为空时,说明队列为空

 代码实现

import java.util.Stack;

class MyQueue {

    private Stack<Integer> s1 ;
    private Stack<Integer> s2 ;

    public MyQueue() {
        s1 = new Stack<>();
        s2 = new Stack<>();
    }
    
    public void push(int x) {
        s1.push(x);
    }
    
    public int pop() {
        if(empty()) {
            return -1;
        }
        if(s2.isEmpty()) {
            //弹出s1当中所有的元素 放到s2中
            while(!s1.isEmpty()) {
                s2.push(s1.pop());
            }
        }
        return s2.pop();
    }
    
    public int peek() {
        if(empty()) {
            return -1;
        }
        if(s2.isEmpty()) {
            //弹出s1当中所有的元素 放到s2中
            while(!s1.isEmpty()) {
                s2.push(s1.pop());
            }
        }
        return s2.peek();
    }
    
    public boolean empty() {
        return s1.isEmpty() && s2.isEmpty();
    }
}

感谢观看,希望对你有所帮助! ! !

  • 31
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 40
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 40
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A小码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值