队列Queue

一、队列(先进先出)

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

Queue是个接口,底层通过链表实现,实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。

导包:

import java.util.Queue;

import java.util.LinkedList;

public static void main(String[] args) {
        Queue<Integer> queue=new LinkedList<>();
        //入队列
        queue.offer(1);
        queue.offer(3);
        queue.offer(5);
        //出队列
        System.out.println(queue.poll());
        //获取队头元素 3
        System.out.println(queue.peek());
        //获取队列中有效元素个数
        System.out.println(queue.size());
        //判断队列是否为空
        if(!queue.isEmpty()){
            System.out.println("队列不为空");
        }

 方法                                            功能

boolean offer(E e)                        入队列

E poll()                                         出队列 

peek()                                          获取队头元素 

int size()                                      获取队列中有效元素个数 

boolean isEmpty()                       检测队列是否为空

双端队列

  //双端队列
        Deque<Integer> queue2 =new LinkedList<>();

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

二、链表模拟实现队列

链式结构实现队列(链表)

优点:
  1. 动态扩容:不需要预先分配大小,按需创建节点,节省空间。
  2. 没有溢出问题:只要系统有内存,就可以一直添加元素。
  3. 更适合频繁插入删除的场景
缺点:
  1. 访问速度慢:节点可能分布在不连续的内存中,不利于缓存优化。
  2. 实现稍复杂:需要维护头尾指针,处理节点的连接与释放。

import java.util.Scanner; 
import java.util.Queue;
import java.util.LinkedList;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        Queue<Integer>queue=new LinkedList<>();
        int n=in.nextInt();
        for(int i=0;i<n;i++){
            String a=in.next();
            if(a.equals("push")){
                int num=in.nextInt();
                queue.offer(num);
            }else if(a.equals("pop")){
                    if(queue.isEmpty()){
                        System.out.println("error");
                    }else{
                        Integer x=queue.poll();
                         System.out.println(x);
                    }
          }else{
             if(queue.isEmpty()){
                        System.out.println("error");
                    }else{
                        Integer x=queue.peek();
                         System.out.println(x);
                    }
        }
    }
}
}

三、循环队列练习题

错误代码

import java.util.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        Queue<Integer> queue=new LinkedList<>();
     int n=in.nextInt();
     int k=in.nextInt();
     //计数器,判断循环队列是否满
     int m=0;
     for(int i=0;i<k;i++){
        String s=in.next();
        if(s.equals("push")){
            if(m<n){
                //m=n队列满了,不能在入队了
                int num=in.nextInt();
                queue.offer(num);
                m++;
            }else{
                System.out.println("full");
            }

        }else if(s.equals("front")){
            if(queue.isEmpty()){
                System.out.println("empty");
            }else{
               Integer a= queue.peek();
                System.out.println(a);
            }
        }else{
             if(queue.isEmpty()){
                System.out.println("empty");
            }else{
               Integer a= queue.poll();
                System.out.println(a);
                m--;
            }

        }
     }
      
    }
}

本质上错误的核心问题是:你使用了 LinkedList 来模拟循环队列,而它不是真正的循环结构
假设 n=1,我们执行如下操作:

push 9    // 入队成功
pop       // 出队成功
push -18  // 理应再次入队成功(因为前面出队了一个)

但你的程序输出却是:

full

为什么?

因为你用 m < n 来判断是否可以 push,但你只在 push 时增加 m++,在 pop 时减少 m--。这看起来是对的,但是你没有真正释放空间给下一次 push 使用!

也就是说:

  • m 是一个模拟的容量限制;
  • 但底层 queue.offer() 实际上是无限扩容的;
  • 所以即使你把 m 控制得再好,也无法体现“空间复用”这一循环队列的本质。

而你的代码使用的是 Java 的 Queue<Integer> queue = new LinkedList<>();,这是一个动态扩容、不支持空间复用的数据结构。

但如果你使用 LinkedList,即使你加了 m--,你也无法知道哪些空间是空闲的,导致第二次 push 被误判为 full

必须手动实现循环队列

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int p = in.nextInt();
        int [] data = new int[n];
        int front = 0; //队头指针
        int rear = 0; //队尾
        int count = 0;
        for (int i = 0; i < p; i++) {
            String a = in.next();

            if (a.equals("push")) {
                    int num = in.nextInt(); //push后面一定是个整数,要放在这个位置,否则遗漏整数占用次数p
                if (count == n) {
                    System.out.println("full");
                } else {
                    //int num = in.nextInt();//在这里报错
                    data[rear] = num;
                    rear = (rear + 1) % n;
                    count++;
                }

            } else if (a.equals("front")) {

                if (count == 0) {
                    System.out.println("empty");
                } else {
                    System.out.println(data[front]);
                }

            } else if (a.equals("pop")) {
                if (count == 0) {
                    System.out.println("empty");
                } else {
                    System.out.println(data[front]);
                    front = (front + 1) % n;
                    count --;
                }

            }
        }
    }
}

实现循环队列核心语句

它们用于在队列中实现“循环移动指针”的效果,即:

  • 当 rear 指针到达数组末尾时,自动回到数组开头;
  • 当 front 指针到达数组末尾时,也自动回到数组开头

 为什么用 % n

使用模运算 % n 是为了实现指针的“循环”行为:

  • 当指针走到数组末尾时(如 index = 2),下一次加 1 应该回到数组起点(index = 0)
  • 所以 (index + 1) % n 可以保证指针始终在 [0, n-1] 范围内循环
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值