【数据结构_9】栈和队列

队列 Queue 一个方向进,一个方向出

Queue队列提供的核心方法:

入队列:offer add

出队列:poll remove

取队首元素: peek element

前面一列发生错误是返回null 后面一列发生错误时抛出异常

Queue是否能够使用isEmpty()/size 等这样的方法呢?

答案:是可以的,因为Queue接口继承自Collection接口,而Collection接口实现了这一系列方法。

因此,判定队列是否为空也就有了两种表示方法:

java 复制代码
        //判定队列是否为空
        if(queue.peek()==null){

        }
        if(queue.isEmpty()){
            
        }

一、模拟实现队列

java 复制代码
package Queue;
//基于单链表实现的队列
public class MyQueue {
    //链表的一个节点
    public class Node{
        public String val;
        public Node next;

        //提供构造方法
        public Node(String val) {
            this.val = val;
            this.next = null;
        }
    }
    //把队列的头部和尾部都记录下来
    //基于俩表实现队列
    //1.入队--->尾插
    //2.出队--->尾删

    private Node head;
    private Node tail;

    //入队
    public void offer(String val) {

        Node newNode = new Node(val);
        //考虑特殊情况 如果链表中没有元素
        if(head== null){
            head = newNode;
            tail = newNode;
            return;
        }
        //一般情况
        tail.next = newNode;
        tail = newNode;
    }

    //出队列
    public String poll(){
        //如果链表是空的
        if(head == null){
            return null;
        }
        //一般情况
        //保存头部节点的值
        //接下来把这个节点删除后,需要返回这个值
        String val = head.val;
        head = head.next;
        //如果链表的节点数目超出一个,删掉一个元素,不影响tail的指向
        //但是,如果链表的节点数目只有一个,删掉这个元素,此时tail就应该指向null
        if(head.next== null){
            tail = null;
        }
        return  val;
    }

    //取队首元素
    public String peek(){
        //如果链表是空的
        if(head == null){
            return null;
        }
        return head.val;
    }

    //判断队列是否为空
    public Boolean isEmpty(){
        return head == null;
    }

    //计算队列的长度
    public int size(){
        int size = 0;
        for(Node cur = head;cur!= null;cur =cur.next){
            size++;
        }
        return size;
    }

    //一些测试
    public static void main(String[] args) {
        MyQueue queue = new MyQueue();
        queue.offer("a");
        queue.offer("b");
        queue.offer("c");
        queue.offer("d");
        queue.offer("e");
        System.out.println(queue.peek());
        System.out.println(queue.poll());
        System.out.println(queue.peek());
        System.out.println(queue.poll());
    }
}

二、数组实现队列

java 复制代码
        //这是一个基于数组的队列
        Queue<Integer> queue1 = new ArrayDeque<>();
        //基于数组实现的双端队列

首先先创建一个数组new int[8]

和顺序表设定类似,不是一上来这8个格子都被使用了,而是随着后续入队列逐渐使用。

由于是一个队列,使用head下标记录队首位置,使用tail下标记录队尾元素的下一个位置。

初始情况下,head 和tail都指向0位置,此时认为是一个空的队列。

队列能入也能出,出队列,得从head的位置进行考虑,如果是按照顺序表头删的做法,此时就需要搬运大量的元素,实现队列的效率就太低了。

所以这里的出队列我们选择用逻辑删除:这里的删除,不是真的把数据给改成别的,而是在逻辑上将其标记成无效。

此处也是往后移动head的位置,由于[head,tail)构成前闭后开开区间,当head++之后,之前head指向元素就被视为无效了。

当tail到达数组末尾,此时是否就意味着队列满了呢?并非。数组版本的队列,就像是把数组弯过来,头尾相接,构成了一个环。也就是"循环队列"

如果在这个循环队列中,队列满了怎么办?有人说,可以通过head 和tail是否重合来判断队列是否满了,但是最初队列为空的时候,head和tail也是重合的。

如何区分上述队列是空的还是满的呢?

此时我们有两种方案来解决这个问题:

方案一:直接浪费一个格子,当tail走到head的前一个位置的时候哦,就视为队列满了,确保再队列满的时候,tail就是head的前一个位置。队列为空的时候,tail与head才重合。

方案二:引入一个size变量即可,size ==0 就是空 size = arr.length就是满了

虽然队列是有 基于链表 和 基于数组两种风格,实际开发中,基于数组的方案是用的更多的。

数组这种方案的优点是什么呢?

1.拥有更高的效率:入队列/出货队列就是简单的head++ tail++ 执行速度更快。这时候就有人想问:"链表,不也就是修改一下引用的指向,时间复杂度也是(1)?为什么说基于数组的执行速度更快?"原因是:链表在进行访问下一个元素的时候,需要多义词间接寻址(先读取引用的值,得到了地址,再根据地址找到对应内存空间)由此处我们可以知道,效率的高与低,运行速度的快和慢都是相对的。

2.对于队列中元素个数的上限是可控的:对于链表版本来说,无限地往里面插入元素,只要你元素够,就可以插入。但是如果你的代码吹按bug了,不小心再入队列,这里就会出现死循环。此时链表版本,不会直接报错,而是把所有的内存都耗尽,导致严重的后果(比如整个程序瘫痪了)但是,如果是数组版本,并且不去自动扩容的话,如果出现类似的问题,后续就能够再入队列的时候及时报错,把问题影响范围缩小。

3.数组版本的队列,内存使用率是更高的:链表版本,由于需要保存额外的next引用,导致内存的利用率更加低。

实现代码:

java 复制代码
package Queue;

public class MyQueueByArray {
    //首先创建一个数组
    private String[] arr = null;
    //队首
    private int head = 0;
    //队尾
    private  int tail = 0;
    //队列的元素个数
    private int size = 0;
    //来一个构造方法
    public MyQueueByArray(){
        arr = new String[1000];
    }
    //再来一个给定参数的构造方法
    public MyQueueByArray(int capacity){
        arr = new String[capacity];
    }

    //1.入队列操作
    public void offer(String val){
        //如果队列满了 直接返回
        if(size == arr.length){
            return;
        }
        //把新的元素,放到tail的位置
        arr[tail] = val;
        //更新tail的指向
        tail++;
        if(tail == arr.length){
            tail =0;
        }
        //更新tail的指向,其实还有另外一种写法
        //更推荐上面的写法,而不是这里的 % 的写法
        //tail=(tail+1)%arr.length;
        size++;
    }

    //2.出队列操作
    public String poll(){
        //如果队列为空,直接返回null
        if(size ==0){
            return  null;
        }
        //取出队首元素 保存起来 以便接下来返回值
        String elem = arr[head];
        head++;
        //更新head的指向并且进行判断
        if(head == arr.length){
            head = 0;
        }
        size--;
        return elem;
    }

    //3.查看队首元素
    public String peek(){
        if(size ==0){
            return  null;
        }
        return arr[head];
    }

    //4.判断队列是否为空
    public Boolean isEmpty(){
        return  size ==0;
    }

    //5.获取队列长度
    public int size(){
        return size;
    }

    //测试
    public static void main(String[] args) {
        MyQueueByArray myQueueByArray = new MyQueueByArray();
        myQueueByArray.offer("a");
        myQueueByArray.offer("b");
        myQueueByArray.offer("c");
        myQueueByArray.offer("d");

        System.out.println(myQueueByArray.peek());
        System.out.println(myQueueByArray.poll());
        System.out.println(myQueueByArray.poll());

    }
}

三、双端队列

双端队列,虽然是叫"队列",但他也能当作"栈"来使用,addLast搭配removeLast,相当于栈

addFirst 搭配 removeLast 相当于队列

双端队列的实现:

java 复制代码
public class Test2 {
    public static void main(String[] args) {
        //创建双端队列
        //Deque<Integer> deque = new ArrayDeque<>();
        Deque<Integer> deque = new LinkedList<>();
        //对于Queue提供的各种功能,deque也都是支持的
        //除此之外,Deque提供了其他的功能
    }
}

四、有关队列的OJ题

实现思路:

1.准备两个队列A,B

2.入栈:先把A中的所有元素往B里面倒腾(A循环出队列,把出来的元素,入队列到B中)当A中就剩最后一个元素的时候,把这个元素当作栈的元素,删除掉就可以了

当完成一次出栈,所有的元素都被倒腾到B这个队列中了,此时就可以交换A和B的指向,后续如果再需要入栈操作,还是继续往A中添加

4.取栈顶元素:和出栈类似,把A中的元素往B里倒腾,倒腾的过程中,当就剩一个元素的时候,把这个元素的值返回,接下来继续把这个值添加到B中的,然后还是交换A和B

代码段:

java 复制代码
// 通过两个队列实现栈.
public class MyStack {
    private Queue<Integer> A = new LinkedList<>();
    private Queue<Integer> B = new LinkedList<>();

    public MyStack() {

    }

    private void swap() {
        Queue<Integer> tmp = A;
        A = B;
        B = tmp;
    }

    public void push(int x) {
        // 入栈的时候
        // 把 x 添加到队列 A 中.
        A.offer(x);
    }

    public int pop() {
        // 出栈的时候
        // 判定一下是否为空
        if (empty()) {
            // 为空, 直接返回.
            return 0;
        }
        // 把 A 中的元素往 B 里面倒腾. 直到 A 中就剩最后一个元素的时候, 这个元素就可以被删除了.
        // 循环结束, 就剩一个元素.
        while (A.size() > 1) {
            int n = A.poll();
            B.offer(n);
        }
        // 循环结束, 说明 A 中就剩一个元素了. 最后这个元素不能插入到 B 中.
        int ret = A.poll();
        // 交换 A 和 B.
        swap();
        return ret;
    }

    public int top() {
        if (empty()) {
            // OJ 题不能抛出异常. 并且也不能修改 返回值类型为 Integer 此时也无法返回 null. 只能返回 0.
            // 题目本身应该不会有栈为空再 top 的情况.
            return 0;
        }
        // 取栈顶元素, 也是把 A 的元素往 B 里倒腾.
        while (A.size() > 1) {
            int n = A.poll();
            B.offer(n);
        }
        // 取出最后一个元素
        int ret = A.poll();
        // 把最后一个元素添加到 B 中. (和 pop 相比, 就只是这里多了一行, 别的地方都一样) .
        B.offer(ret);
        // 交换 A 和 B.
        swap();
        return ret;
    }

    public boolean empty() {
        // 会交换 A 和 B. 所以 B 始终为 空的 . 抓住 A 的空就可以判定整体的 空.
        return A.isEmpty();
    }


}
/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack obj = new MyStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * boolean param_4 = obj.empty();
 */

思路:创建两个栈

1.入队列:把所有B中的元素倒腾到A,往A中入栈

2.出队列:把所有A的元素倒腾给B,从B出栈

3.取队首元素:也是把A的元素倒腾给B,取B的栈顶

4.判定空,确保两个栈都不空,此时整体为空

代码段:

java 复制代码
// 使用两个栈, 模拟实现队列.
public class MyQueue {
    // 创建两个栈
    // A 用于入队列, B 用于出队列.
    Stack<Integer> A = new Stack<Integer>();
    Stack<Integer> B = new Stack<Integer>();

    public void push(int x) {
        // 先把 B 中的所有元素倒腾到 A 里, 然后把元素添加到 A 中.
        while (!B.isEmpty()) {
            A.push(B.pop());
        }
        A.push(x);
    }

    public int pop() {
        // 先把 A 中的所有元素倒腾到 B 里, 然后弹出 B 栈顶元素.
        while (!A.isEmpty()) {
            B.push(A.pop());
        }
        return B.pop();
    }

    public int peek() {
        // 先把 A 的所有元素倒腾到 B 里, 取 B 的栈顶元素.
        while (!A.isEmpty()) {
            B.push(A.pop());
        }
        return B.peek();
    }

    public boolean empty() {
        return A.isEmpty() && B.isEmpty();
    }
}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */
相关推荐
yzlAurora9 小时前
删除链表倒数第N个节点
数据结构·链表
进击的小白菜10 小时前
如何高效实现「LeetCode25. K 个一组翻转链表」?Java 详细解决方案
java·数据结构·leetcode·链表
拾忆-eleven10 小时前
C++算法(19):整数类型极值,从INT_MIN原理到跨平台开发实战
数据结构·c++·算法
我是一只鱼022312 小时前
LeetCode算法题 (反转链表)Day17!!!C/C++
数据结构·c++·算法·leetcode·链表
ice___Cpu14 小时前
数据结构 - 10( B- 树 && B+ 树 && B* 树 4000 字详解 )
数据结构
SuperCandyXu14 小时前
004 树与二叉树:从原理到实战
数据结构·算法
繁星蓝雨15 小时前
Qt中数据结构使用自定义类————附带详细示例
数据结构·c++·qt·qmap·qset
士别三日&&当刮目相看16 小时前
数据结构*二叉树
数据结构
喝养乐多长不高16 小时前
数据结构--红黑树
java·数据结构·算法·红黑树·二叉搜索树·avl树
阿沁QWQ17 小时前
list的设计
数据结构·list