Java数据结构栈和队列(Stack和Queue详解)

前言:

        栈和队列是数据结构中重要的存储方式,也是建立在Arrarylist和LinkedList之上的数据结构。

        本猿在C语言阶段就已经详细剖析过栈和队列,不熟悉的小伙伴可以翻看之前的博客内容!

      Java阶段一方面剖析Java中自带的Stack和Queue中的各种方法,另一方面也是重点重新再次实现栈和队列,  并发掘和C语言中的区别。

栈(Stack):

        栈是一种"先进后出"的数据结构,如图:

Java中的栈:

        在Java的util包中有Stack类,我们可以看到Stack的源代码:

继承了Vector,那么Vector中又有哪些继承关系呢?

我们发现继承了AbstractList,并且扩展了好多接口,其中包括List接口。

综上我们可以以一张结构图概括:

栈的模拟实现:

        我们利用顺序表模拟实现栈,当然也可以用链表实现栈,Java中用的是顺序表!至于为什么要用顺序表,等自己实现一遍栈大概就清楚了!

    

public class MyStack {
    private int[] array;
    private int size;
    private static final int DEFFAULEVALUE = 10;
    public MyStack() {
        array = new int[DEFFAULEVALUE];
    }
    public void push(int value) {
    ensureCapacity();
    array[this.size++] = value;
    }
    public int pop(){
        if(empty()) {
            throw new RuntimeException("栈为空,无法获取栈顶元素");
        }
        size--;
        return array[size];
    }
    public int peek(){
        if(empty()){
            throw new RuntimeException("栈为空,无法获取栈顶元素");
        }
        return array[size-1];
    }
    public int size(){
        return size;
    }
    
    private boolean empty() {
        if(this.size == 0) {
            return true;
        }
        return false;
    }
    private void ensureCapacity() {
        if(this.size == array.length) {
            array = Arrays.copyOf(array,size*2);
        }
    }
}

注意:

        在main方法中测试的时候,需要注意打印栈当中的元素的时候需要pop打印,也就是需要用到for循环打印,但是这样写对不对呢?

        

for (int i = 0; i < myStack.size(); i++) {
    System.out.println(myStack.pop());
}

这样的for循环是有问题的,问题在于每次pop结束之后,size()的值在发生变化,也就是这个循环不会执行size()次。

        所以以上遍历会出现问题,应该这样遍历:

int num = myStack.size();
for (int i = 0; i < num; i++) {
    System.out.println(myStack.pop());
}

这样遍历才不会出现问题!! 

关于栈的习题(典型):

1、括号匹配问题:

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

        具体题目请参考力扣,具体实现代码如下:

        

class Solution {
    public boolean isValid(String s) {
        if(s == null) {
            return true;
        }
        Stack<Character> stack = new Stack<>();
        for(int i = 0;i<s.length();i++) {
            char a = s.charAt(i);
            if(stack.empty()) {
                if(a == ')' || a == ']' || a == '}') {
                    return false;
                }
            }
            if(a == '(' || a == '{' ||a == '[') {
                stack.push(a);
            }else {
                char b = stack.pop();
                if(!(a == ')' && b == '(' || a == ']' && b == '[' || a == '}' && b == '{')) {
                    return false;
                }
            }
        }
        if(!stack.empty()) {
            return false;
        }
        return true;
    }
}

2、逆波兰表达式求值:

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

class Solution {
       public int evalRPN(String[] tokens) {
        Stack<String> str = new Stack<>();
        for(int i = 0;i < tokens.length;i++) {
            if(tokens[i].equals("+")) {
                int num1 = Integer.valueOf(str.pop());
                int num2 = Integer.valueOf(str.pop());
                str.push(num1+num2+"");
            }else if(tokens[i].equals("-")) {
                int num1 = Integer.valueOf(str.pop());
                int num2 = Integer.valueOf(str.pop());
                str.push(num2-num1+"");
            }else if(tokens[i].equals("*")) {
                int num1 = Integer.valueOf(str.pop());
                int num2 = Integer.valueOf(str.pop());
                str.push(num1*num2+"");
            }else if(tokens[i].equals("/")) {
                int num1 = Integer.valueOf(str.pop());
                int num2 = Integer.valueOf(str.pop());
                str.push(num2/num1+"");
            }
            else {
                str.push(tokens[i]);
            }

        }
        return Integer.valueOf(str.pop());
    }
}

3、出栈进栈次序匹配:

栈的压入、弹出序列_牛客题霸_牛客网 (nowcoder.com)

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pushV int整型一维数组 
     * @param popV int整型一维数组 
     * @return bool布尔型
     */
    public boolean IsPopOrder (int[] pushV, int[] popV) {
        // write code here
        Stack<Integer> st = new Stack<>();
        int j = 0;
        for(int i = 0;i<pushV.length;i++) {
            st.push(pushV[i]);
            while(!st.empty() && st.peek() == popV[j]) {
                st.pop();
                j++;
            }
        }
        if(st.empty()) {
            return true;
        }
        return false;
}
}

4、最小栈:

155. 最小栈 - 力扣(LeetCode)

class MinStack {
    Stack<Integer> st1;
    Stack<Integer> st2;
    int minInteger;
    public MinStack() {
        st1 = new Stack<>();
        st2 = new Stack<>();
    }

    public void push(int val) {
        if(st1.empty() && st2.empty()) {
            minInteger = val;
            st1.push(val);
            st2.push(val);
        }else {
            st1.push(val);
            if(val < minInteger) {
                st2.push(val);
                minInteger = val;
            }else {
                st2.push(minInteger);
            }
        }
    }

    public void pop() {
        st1.pop();
        st2.pop();
        if(!st2.empty()) {
        minInteger = st2.peek();
        }
    }

    public int top() {
        return st1.peek();
    }

    public int getMin() {
        return st2.peek();
    }
}

队列(Queue):

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

Java中的队列:

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

也是一系列的继承关系。

队列的模拟实现:

public class MyQueue {
    // 双向链表节点
    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 -1;
        }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 0;
        }
        return first.value;
    }
    public int size() {
        return size;
    }
    public boolean isEmpty(){
        return first == null;
    }
}

循环队列:

        实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。 环形队列通常使用数组实现。
数组下标循环的小技巧
1. 下标最后再往后 (offset 小于 array.length): index = (index + offset) % array.length
2. 下标最前再往前 (offset 小于 array.length): index = (index + array.length - offset) % array.length

如何区分空与满:

1. 通过添加 size 属性记录
2. 保留一个位置
3. 使用标记
接下来我们就来实战一下:
要求:
分别用三种不同的区分空和满的方式:
方式一:
class MyCircularQueue {
    private int[] arr;
    private int size;
    private int rear;
    private int front;
    public MyCircularQueue(int k) {
        arr = new int[k];
        size = 0;
    }
    
    public boolean enQueue(int value) {
        if(isFull()) {
            return false;
        }
        arr[rear] = value;
        size++;
        rear = (rear+1)%arr.length;
        return true;
    }
    
    public boolean deQueue() {
        if(isEmpty()) {
            return false;
        }
        front = (front+1)%arr.length;
        size--;
        return true;
    }
    
    public int Front() {
        if(isEmpty()) {
            return -1;
        }
        return arr[front];
    }
    
    public int Rear() {
        if(isEmpty()) {
            return -1;
        }
        return arr[(rear+arr.length-1)%arr.length];
    }
    
    public boolean isEmpty() {
        if(size == 0) {
            return true;
        }
        return false;
    }
    
    public boolean isFull() {
        if(size == arr.length) {
            return true;
        }
        return false;
    }
}

方式二:

class MyCircularQueue {
    private int[] arr;
    private int rear;
    private int front;
    public MyCircularQueue(int k) {
        arr = new int[k+1];
        rear = 0;
        front = 0;
    }
    
    public boolean enQueue(int value) {
        if(isFull()) {
            return false;
        }
        arr[rear] = value;
        rear = (rear+1)%arr.length;
        return true;
    }
    
    public boolean deQueue() {
        if(isEmpty()) {
            return false;
        }
        front = (front+1)%arr.length;
        return true;
    }
    
    public int Front() {
        if(isEmpty()) {
            return -1;
        }
        return arr[front];
    }
    
    public int Rear() {
        if(isEmpty()) {
            return -1;
        }
        return arr[(rear+arr.length-1)%arr.length];
    }
    
    public boolean isEmpty() {
        if(rear == front) {
            return true;
        }
        return false;
    }
    
    public boolean isFull() {
        if((rear+1)%arr.length == front) {
            return true;
        }
        return false;
    }
}

方式三:大家可以自己试试!

双端队列:

        双端队列(deque )是指允许两端都可以进行入队和出队操作的队列, deque “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。
        此时该场景应该有几种组合:
        对头出,对头进。
        对头出,对尾进。
        对尾出,对头进。
        队尾出,对头出。
        也就是该双端队列既可以当作栈也可以当作队列!!
        Deque 是一个接口,使用时必须创建 LinkedList 的对象。
Deque<Integer> stack = new ArrayDeque<>();// 双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();// 双端队列的链式实现

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值