Java 数组|链表实现队列


当然队列和栈拥有的操作一样,只有入队列和出队列两个操作:

  • 入队列:在队尾加入一个元素
  • 出队列:在队头取出一个元素

就像链表可以自由在中间任意某个位置插入和删除,而栈和队列只能在固定的位置操作。这其实就是创造者们所定义的,我们只有给予其一定的限制,才能获得我们想要的特性。

在这里插入图片描述

具体实现

顺序队列

当然,用数组实现的队列叫顺序队列,接下上完整代码吧。

public class Queue {
    private String[] datas;//数组
    private int n;//数组大小
    private int head;//队头下标
    private int tail;//队尾下标

    //初始化一个队列
    public Queue(int capacity) {
        this.datas = new String[capacity];
        this.n = capacity;
    }

    //入队操作
    public void enQueue(String data) {
        if (n == tail) {
            System.out.println("队列已满");
        } else {
            datas[tail] = data;
            tail++;
        }
    }

    //出队操作
    public String deQueue() {
        if (head == tail) {
            System.out.println("队列为空");
            return null;
        } else {
            String temp = datas[head];
            head++;
            return temp;
        }
    }

    //判断队列是否为空
    public boolean isEmpty() {
        if (head == tail)
            return true;
        return false;
    }

    public static void main(String[] args) {
        Queue queue = new Queue(5);
        queue.enQueue("1");
        queue.enQueue("2");
        queue.enQueue("3");
        while (!queue.isEmpty()) {
            System.out.println(queue.deQueue());
        }
    }
}

数组实现的队列代码如上,示意图如下:

我们看源码的时候,有时候总说底层是啥实现的,其实就是要主要他们的特性,比如我这里底层是「数组」实现的,**那么我们在写代码的时候就要遵循底层(数组)的规律。**而不是像示意图那样,队尾插入就真的是从右往左添加数据。

在这里插入图片描述

我们对这个图片的操作进行截图:

  • 初始化:没啥好说的,就是申请了一个空数组。

  • enQueue(入队操作):tail++:表示一个一个往后面加,

  • 而不是像这张图中最上面的示意图,直接从右边往左边加,这就是示意图和具体代码的具体。tip:我们使用数组实现的哦,数组就是从0开始往后加

  • deQueue(出队操作):head++:表示一个一个出队列(数据还存在在数组中,只是我们访问不到了)

Tip:当 head=tail 时,就表示数组为空,此时不能出队列tip:那当 tail 指向数组最后一个位置了呢?即 n=tail 那就是数组用完了,不能插入

数组有空闲但是不能用

呃呃,这个问题就是上面的当我的 head=tail,其实此时 head 以前的位置都已经“出队列”了,所以以前的位置还可以使用,那么该怎么办呢。

对,就是数据搬移,将此时head后面位置的元素移动从下标为 0 开始的位置。如下图:

在这里插入图片描述

因为我们只需要在入队的时候判断是否有空间,所以我们只要改造 enQueue() 函数即可。当没有空间的时候(即:tail = n ),我们再一次性的搬移。

n:是初始化数组的长度。

 //入队操作
    public void enQueue(String data) {
        if (n == tail) {
            if (head == 0) {//就是整个队列满了,没法搬移
                System.out.println("队列已满");
            } else {
                for (int i = head; i < tail; i++) {
                    //此时数组剩下的所以元素:即有 head到tail 个
                    datas[i - head] = datas[i];
                }
                //数据搬移完,记得改变“指针”位置哦
                tail = tail - head;
                head = 0;
            }
        }
        datas[tail] = data;
        tail++;
    }
    
   
      public static void main(String[] args) {
        Queue queue = new Queue(5);
        queue.enQueue("1");
        queue.enQueue("2");
        queue.enQueue("3");
        queue.enQueue("4");
        queue.enQueue("5");

        queue.deQueue();
        queue.deQueue();

        for (int i = queue.head; i < 5; i++) {
            System.out.println(queue.datas[i]);
        }
    }

上面我只贴出了enQueue() 函数,和测试代码

enQueue() :

  • 判断(n == tail && head == 0)是否成立;
  • 搬移元素,并修改 head/tail 指针指向的位置

测试代码:

  • 将数组插满
  • 删除两个元素
  • 从此时的 head 指向的位置看是遍历: for (int i = queue.head; i < 5; i++);而不是 for (int i = 0; i < 5; i++);后者会打印出12345;why?我们不是将数组移除队列了吗?
  • 其实我们在内存中分配的数据还在这个数组里面没有变,也没有移动。我们只是通过移动 “指针” 去访问指定的位置,因为这是队列,我们是无法访问 head 以前的数据,所以我们把它看成是被移除队列,但它其实还真实的存在这个数组里面。

链式队列

class Node {
    String datas;
    Node next;

    public Node() {
    }

    public Node(String data) {
        this.datas = data;
    }
}

public class Queue {
    private Node head = null;
    private Node tail = null;

    //入队列
    public void enQueue(String data) {
        Node dataNode = new Node(data);
        if (head == null) {//当队列为空时,把 head 和 tail 都同时赋给第一个节点
            //这里就保证了遍历此链表时:head 是第一个节点
            head = dataNode;
            tail = dataNode;
        } else {
            tail.next = dataNode;
            tail = dataNode;
        }
    }

    //出队列
    public String deQueue() {
        String temp;
        if (head != null) {//有元素
            temp = head.datas;
            head = head.next;
            return temp;
        } else {
            return null;
        }
    }

    public void print() {
        Node p = head; //在验证代码时,防止 print 一次,链表中“没数据”了
        while (p != null) {
            System.out.print(p.datas + "->");
            p = p.next;
        }
        System.out.println("null");
    }

    public static void main(String[] args) {
        Queue queue = new Queue();
        queue.enQueue("1");
        queue.enQueue("2");
        queue.enQueue("3");

        queue.print();
        queue.deQueue();
        queue.print();//如果 print 不用临时变量替代 head,那么下面都会输出位 null
        queue.deQueue();
        queue.print();
    }
}

分析:

当为空的时候,我们把 head/tail 节点都赋给第一个节点。

入队:

只需要移动 tail 即可,因为第一次赋值就保证了 head 和 tail 在同一个链表上

出队:

head 往后移即可

扩展

这里主要是先先实现基本的 Java队列功能,饭要一口一口吃,路呀一步一步走嘛。

对于扩展呢,主要是 Java 中集合库中使用了队列这种数据结构,比如:JDK 线程池中的 BlockingQueue

后续再补充诸如 循环队列、阻塞队列并发队列等,还可以加上一波对 JDK 底层源码解析。

总结

  • 学会用画图辅助自己思考,可能有时候只靠想,脑子会不够用哦。画图是真香啊
  • 不要觉得自己看懂了,我要我觉得你应该去自己独立的写出来。
  • 如果觉得自己代码没有问题,但是结果并不是自己想象那样的,那么请 Debug。IDEA Debug 了解一下。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值