Java集合框架:队列、Queue和Deque详解

目录

一、普通队列

1. 概念

2. Queue(Java集合框架的接口)

3. Queue中的方法

4. 方法使用演示

5. 队列的模拟实现

6. 顺序普通队列的缺点:

 二、循环队列

1. 循环队列也是一种数据结构。基于上述队列的缺点,此时就有了循环队列,如下图:

2. 是一个类似圆形的数组:

3. 所以此时有两个问题:   

4. 循环队列的实现:  (oj题链接:力扣)

三、双端队列(Deque)

1. 概念

2. 双端队列的使用

3. 面试题

两个队列实现一个栈:

两个队列实现一个栈:


前言

    队列是一种数据结构,在Java集合框架中,有对应的实现的接口,Queue是一个队列规范的接口,Deque是一个双端队列实现的接口,由于两个都是接口,所以是不能直接进行是实例化的,需要接口引用具体的类来进行实例化,所以底层可以是顺序表(数组),也可以是链表(单 / 双链表)。

一、普通队列

1. 概念

    队列:只可以在一段进行插入操作,在另一端进行删除操作的线性表,队列具有先进先出的特性,在插入操作的一端称作队尾,进行删除操作的一端称作队头。

 2. Queue(Java集合框架的接口)

     如上图:Queue(队列)是一个接口,底层是一个双向链表来实现的,所以Queue不能直接实例化一个对象,只能是接口引用一个具体类(LinkedList)的方式来实例化。

3. Queue中的方法

boolean offer(E  e)入队列
E poll()出队列
peek()获取队头元素
int size()获取队列中的有效元素个数
boolean isEmpty()判断队列是否为空

4. 方法使用演示

public static void main(String[] args) {
        //尾插法和头部删除
        MyQueue queue = new MyQueue();
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        System.out.println(queue.peek());//1
        System.out.println(queue.poll());//1
        System.out.println(queue.peek());//2
        System.out.println(queue.isEmpty());//false
        System.out.println(queue.usedSize);//3
    }

5. 队列的模拟实现

     注:双向链表是最合适实现一个队列的,双向链表可以在尾部插入,也可以在尾部进行删除,因为每个节点都有前驱和后继节点,插入和删除的时间复杂度都是O(1),但是如果是单链表此时只能使用尾插法插入,使用头删法(在头部进行删除)时间复杂度才是O(1).

//单链表实现一个队列
public class MyQueue {
    static class Node {
        public int val;
        public Node next;
        public Node(int val) {
            this.val = val;
        }
    }
    public Node head;
    public Node last;
    public int usedSize;
    //入队
    public void offer(int val) {
        Node node = new Node(val);
        if (head == null) {
            head = node;
            last = node;
        }else {
            last.next = node;
            last = node;
        }
        usedSize++;
    }
    //出队
    public int poll() {
        if (isEmpty()) {
            throw new EmptyException("队列是空的!");
        }
        int ret = head.val;
        head = head.next;
        if (head == null) {
            last = null;//如果只有一个节点,那么last也要置空
        }
        usedSize--;
        return ret;
    }
    //判断队列是否为空
    public boolean isEmpty() {
        return usedSize == 0;
    }
    //获取队列队顶元素
    public int peek() {
        if (isEmpty()) {
            throw new EmptyException("队列是空的!");
        }
        return head.val;
    }
    //获取队列有效元素的个数
    public int getUsedSize() {
        return usedSize;
    }
}

6. 顺序普通队列的缺点:

    如下图:此时一个数组实现的顺序队列正在一边入队元素,一边出队元素,如果队列中的元素满了,此时元素再进行出队,但是后边的元素是入不进来的,虽然前面的格子空出来了,但是此时元素只能从队尾进入,队头出,可以看出,此时的队列的利用效率不是很高。

 二、循环队列

1. 循环队列也是一种数据结构。基于上述队列的缺点,此时就有了循环队列,如下图:

2. 是一个类似圆形的数组:

1. 有front指针和rear指针同时指向数组的0下标,入队元素就让rear指针往后走一个,之后出队元素时,就让front指针向后移动。

2. 当循环队列满了,此时rear指针可以往前走一步,到达0下标,然后继续入队元素。

3. 所以此时有两个问题:   

1. 当队列满,如何让rear指针指向0下标?

2. 当队列满时,rear = front;当队列为空时,rear = front,所以如何判断队列的空和满?

    1. 每次入队元素或者出队元素时,让 rear  =(rear + 1)% queue.size(),front =(front + 1)% queue.size(),而不是简单的rear++,front++,所以就可以让rear下标和front下标在走到最后一个位置时,再往后走一步就又到了0下标的位置。

    2. 通过浪费一个空间来区分队列的空和满,如果rear指针的下一个就是front(0下标),此时就认为队列为满,当rear = front时,此时队列为空。(或者使用usedSize来记录当前元素个数也可以)。

4. 循环队列的实现:  (oj题链接:力扣

package Review;
//循环队列底层就是一个数组
//浪费掉最后一个空间来表示队列是否是满的
//就是每次都让rear往后走一步,之后进行判断如果rear的下一个位置就是0下标
//此时队列就是满的,如果front == rear,此时队列是空的
class MyCircularQueue {
    private int[] elem;
    private int front;//队列的头
    private int rear;//队列的尾
    public MyCircularQueue(int k){
        this.elem = new int[k+1];
    }
    public boolean enQueue(int value) {
        //1.检查队列是否是满的
        if (isFull()) {
            return false;
        }
        //2.入队元素,之后让rear引用往后走一步
        elem[rear] = value;
        rear = (rear + 1) % elem.length;
        return true;
    }
    public boolean deQueue() {
        if (isEmpty()) {
            return false;
        }
        //front++;
        front = (front + 1) % elem.length;
        return true;
    }
    public int Front() {
        if (isEmpty()) {
            return -1;
        }
        return elem[front];
    }
    public int Rear() {
        if (isEmpty()) return -1;
        //return elem[rear - 1];
        //此时还有一个问题需要注意,如果rear到了0下标,之后就数组越界了
        //数组是没有-1下标的,
        //也就是让rear返回数组的最后一个下标
        int index = (rear == 0) ? elem.length - 1 : rear - 1;
        return elem[index];
    }
    public boolean isFull() {
        return (rear + 1) % elem.length == front;
    }
    public boolean isEmpty() {
        return front == rear;
    }
}

三、双端队列(Deque)

1. 概念

    双端队列就是可以在两端都可以入队,也可以出队的队列,元素可以从队头入队和出队,也可以从队尾出队和入队。

2. 双端队列的使用

   (在实际使用中,Deque接口使用的是比较多的,栈和队列都可以使用该接口,这个接口中有栈的方法,也有队列的方法)

public static void main9(String[] args) {
        //底层是一个双向链表
        Deque<Integer> deque = new LinkedList<>();
        //数组实现的双端队列:底层就是一个数组
        Deque<Integer> deque1 = new ArrayDeque<>();
        //顺序的双端队列也可以当作栈来使用
        Deque<Integer> stack = new ArrayDeque<>();
        stack.push(1);
        stack.push(2);
        stack.push(3);//顺序的双端队列(底层是用数组来实现的)也提供了栈的相关的方法

    }
    public static void main8(String[] args) {
        //双端队列
        Deque<Integer> deque = new LinkedList<>();
        //普通队列
        //Queue中既有offer方法,也有add方法,add在无法添加一个元素时,会抛出一个异常
        //offer方法优于add方法,如果无法添加元素,offer方法不会抛出异常
        Queue<Integer> queue = new LinkedList<>();
        //链式栈  虽然是具体的类但是里面有栈的方法
        //LinkedList类中的方法是最多的,因为它实现了很多接口,此时一定会重写接口中的方法
        LinkedList<Integer> stack = new LinkedList<>();
        //双向链表
        List<Integer> list = new LinkedList<>();
        //其他的都是使用接口来引用的:只有接口中的方法
    }

3. 面试题

1. 用栈实现一个队列 (oj链接:力扣思路:用两个栈才能实现一个队列,因为栈是先进后出,队列是先进先出;此时需要把栈中的全部元素入栈到第二个栈中,此时的栈顶元素就是出队的元素。
2. 用队列实现一个栈  (oj链接:力扣思路:两个队列实现一个栈,”入栈“开始入到第一个队列中,之后入队就入到不为空的队列中,”出栈“就出到另一个队列中,出size-1个元素,最后剩下的一个元素就是要出栈的元素。

两个队列实现一个栈:

import java.util.LinkedList;
import java.util.Queue;
class MyStack2 {
    private Queue<Integer> qu1;
    private Queue<Integer> qu2;
    public MyStack2() {
        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()){
            //此时一定需要定义一个size来存放qu1的容量,如果直接将size
            //写进for循环中,size一直在改变,就会导致循环的次数减少
            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()){
            //此时一定需要定义一个size来存放qu1的容量,如果直接将size
            //写进for循环中,size一直在改变,就会导致循环的次数减少
            int size = qu1.size();
            int val = -1;
            for (int i = 0; i < size; i++) {
                val = qu1.poll();
                qu2.offer(val);
            }
            return val;
        }else {
            int size = qu2.size();
            int val = -1;
            for (int i = 0; i < size; i++) {
                val = qu2.poll();
                qu1.offer(val);
            }
            return val;
        }
    }
    public boolean empty() {
        return qu1.isEmpty() && qu2.isEmpty();
    }
}

两个队列实现一个栈:

import java.util.Stack;
class MyQueue2 {
    private Stack<Integer> stack1;
    private Stack<Integer> stack2;
    public MyQueue2() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }
    public void push(int x) {
        stack1.push(x);
    }
    public int pop() {
        //前提是两个队列都不能是空的
        if (empty()) return -1;
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
    public int peek() {
        if (empty()) return -1;
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.peek();
    }
    public boolean empty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }
}

  • 8
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Java中的DequeQueue都是集合框架中的接口,但它们有一些区别。 Queue是一个先进先出(FIFO)的数据结构,它只允许在队列的一端插入元素,在另一端删除元素。Queue接口有许多实现,如LinkedList、PriorityQueue等。 而Deque(Double Ended Queue)是一个双端队列,它允许在队列的两端插入和删除元素。Deque接口也有许多实现,如ArrayDeque、LinkedList等。 因此,DequeQueue更加灵活,可以在队列的两端进行插入和删除操作。而Queue只能在队列的一端进行插入和删除操作。 ### 回答2: Deque(双端队列)和Queue队列)是Java中两种不同的数据结构。 首先,Queue是一种先进先出(FIFO)的数据结构。它只能在队列的一端(称为队尾)插入新元素,并从另一端(称为队首)删除元素。Queue的常用实现类有LinkedList和PriorityQueue。 而Deque是一种既可以在队尾插入新元素,也可以在队首删除元素的数据结构。它支持FIFO和LIFO(后进先出)两种操作。在Java中,DequeQueue的子接口。Deque的常用实现类有LinkedList和ArrayDeque。 最明显的区别是,Queue只能在队尾插入元素和在队首删除元素。而Deque除了支持这两种操作外,还可以在队尾删除元素和在队首插入元素。这使得Deque相比于Queue在操作灵活性上更加强大。 此外,由于Deque实现了Queue接口,所以可以在需要Queue的地方使用Deque。也就是说,Deque既可以作为队列使用,也可以作为栈使用(栈是LIFO结构)。 在性能上,由于Deque的实现类LinkedList和ArrayDeque都是使用了双向链表和动态数组,因此它们在插入和删除操作上的性能较好,而LinkedList对于随机访问(根据索引获取元素)的性能较差。 总结来说,Deque相比于Queue更加灵活,既可以作为队列使用,也可以作为栈使用。它允许在队尾和队首进行插入和删除操作,可满足更多的应用需求。而Queue只支持队尾插入和队首删除操作,适用于先进先出的场景。 ### 回答3: Java中的DequeQueue都是用来存储元素的数据结构,但它们有一些区别。 首先,Queue是一个接口,它表示一个具有FIFO(先进先出)特性的数据结构。常见的实现类有LinkedList和PriorityQueue。它提供了一些基本的操作方法,例如插入、删除、查询队列头部元素等。 而Deque(Double Ended Queue)也是一个接口,它继承了Queue接口,并且允许在两端进行插入、删除和查询操作。除了拥有Queue接口的特性外,Deque还提供了一些额外的方法,例如在队列的头部和尾部插入和删除元素,以及获取头部和尾部元素。常见的实现类有LinkedList和ArrayDeque。 因此,主要区别在于Deque是一个双端队列,可以在队列的头部和尾部进行插入、删除和查询操作,而Queue只能在队列的尾部进行插入操作,头部进行删除和查询操作。 另外,Queue接口还提供了一些用于处理特定需求的实现类,例如PriorityQueue可以根据元素的优先级进行排序,ArrayBlockingQueue可以限制队列的容量等。 综上所述,Queue只能在队列尾部进行插入操作,具有FIFO特性;而Deque是一个双端队列,可以在队列的头部和尾部进行插入、删除和查询操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

良月初十♧

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

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

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

打赏作者

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

抵扣说明:

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

余额充值