数据结构与算法--使用Java实现数组队列和循环单链队列

本篇博客所涉及到的代码,均已上传到github

项目github链接
本篇博客涉及代码github链接

上一篇通过Java实现二叉树的博客中:

数据结构与算法–使用Java实现二叉树
我们用到了一点队列的知识
本篇博文,将通过数组,单向循环链表两种方式,使用Java实现队列,
帮助大家进一步了解队列这种数据结构

本篇博客要点如下:

队列

使用Java代码队列

一. 队列

1.1 基本概念

队列: 先进先出(First in First Out)

    队列是一种运算受限的线性表
    只能在队列的一端进行插入,另一端进行删除
    把插入元素的一端称为队尾,删除数据元素的一端称为队首

应用举例 :

	开发中应用: 多线程中的就绪队列和阻塞队列
	现实 : 各种排队场景
1.2 存储结构

包括顺序存储结构(数组)和链式存储结构(链表)

1.2.1 顺序存储结构

若使用顺序存储结构, 队列与ArrayList十分相似(限制ArrayList只能从一端插入,另一端删除,可以把它看做队列)
具体特征可以参考我之前写过的博客,这里不做过多赘述

数据结构与算法–使用Java实现ArrayList

1.2.2 链式存储结构

若用单链,可以参照这篇博客:
数据结构与算法–使用Java实现单链的LinkedList

若用双链,可以参照这篇博客:
数据结构与算法–使用Java实现循环双链的LinkedList

二. 使用Java代码实现队列

2.1 Queue接口

根据队列先进先出FIFO的特征
我们可以总结下关于队列的常用操作:
队列大小,队列非空判断, 元素入队,元素出队,获取队首元素
将这些操作抽象成方法,得到接口如下:

public interface Queue<E> {
    // 返回队列的大小
    int getSize();
    // 队列是否为空
    boolean isEmpty();
    // 数据元素e入队
    void enqueue(Object e);
    // 数据元素出队
    E dequeue();
    // 取队首元素
    E peek();
}
2.2 使用数组实现队列

首先定义两个成员变量:

    private Object[] elementData; // 底层数据结构是数组
    private int size; // 队列长度

给出构造方法:

    public ArrayQueue() {
       this(10); // 数组默认长度为10
    } 
    public ArrayQueue(int size) {
        elementData = new Object[size];
    }
    @Override
    public int getSize() {
        return size;
    }

实现接口里的方法:

 @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public void enqueue(Object e) {
        if (size == elementData.length) {
            // 首先队列满时,应该对队列进行扩容,扩容策略为size * 1.5
            elementData = Arrays.copyOf(elementData, elementData.length * 3 / 2);
        }
        elementData[size] = e; // 从队列尾端插入元素
        size++; // 队列长度 +1
    }

    @Override
    public Object dequeue() {
        if (isEmpty()) { // 队列为空时,不允许进行出队操作
            System.out.println("队列长度为0,无法出队! ");
            return null;
        }
        /*
            先进先出,将后面的元素向前移位,原首元素置为空值
         */
        Object first = elementData[0];
        Object temp = elementData[size - 1];
        for (int i = size - 1 ; i > 0; i --) {
            Object data = elementData[i - 1];
            elementData [i - 1] = temp;
            temp = data;
        }
        elementData[size - 1] = null;
        size --;
        return first; //返回原首元素
    }

    @Override
    public Object peek() {
        return size > 0 ? elementData[0] : null; // 队列非空,返回首元素值,否则返回null
    }

代码测试:

  public static void main(String[] args) {
          Queue queue = new ArrayQueue();
        queue.dequeue(); // 在队列为长度为0的时候,尝试出队
        queue.enqueue("3"); // 数据入队
        System.out.println("队列长度: " + queue.getSize());
        queue.enqueue("4");
        queue.enqueue("5");
        for (int i = 0; i < 100; i ++) {
            queue.enqueue(i); // 数据入队,用于检测数组扩容机制是否正常
        }
        System.out.println("队首元素出队: " + queue.dequeue());
        System.out.println("队列长度: " + queue.getSize());
        System.out.println("队列队首元素为:"  + queue.peek()); // 获取队列首端元素

    }

运行结果:

在这里插入图片描述
从上面的代码中我们可以看到入队,获得队首元素等操作十分快捷
但是出队操作 需要将队首元素后面的元素全部前移一位,
时间复杂度为O(n),这种性能在生产中一定是不允许的

因此我们可以考虑使用链表实现

2.3 使用循环单链实现队列

在之前的博客中,
我介绍了单向链表,双向循环链表,这次我试着使用单向循环链表来实现队列

首先应该引入Node类

public class Node {
    Object data; // 要存储的数据
    Node next; // 指向下一个节点的指针

    public Node(Object data) {
        super();
        this.data = data;
    }

    public Node(Object data, Node next) {
        super();
        this.data = data;
        this.next = next;
    }

    public Node() {

    }


    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }
}

定义必要的成员变量:

	Node head; // 队首元素
    Node tail; // 队尾元素
    int size; // 队列长度

实现队列功能:

 @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public void enqueue(Object e) {
        Node t = tail; // 临时变量保存前队尾元素
        Node node = new Node(e, head); // 新建节点,指针指向队首元素
        if (t == null) { // 队列为空时,队首元素也是队尾元素
            head = node; 
            tail = node;
        } else {
            t.next = node; // 前队尾元素指针指向新的队尾元素
            tail = node; // 队尾元素更新
        }
        size++; // 对列长度增加
    }

    @Override
    public Object dequeue() {
        if (isEmpty()) {
            System.out.println("队列长度为0,无法出队! ");
            return null;
        }
        // 队列长度为1也属于特殊场景,需要单独处理
        if (size == 1) {
            Object data = head.data; // 定义临时变量储存头结点数据
            head = null; // 队首结点置为空
            size --; // 队列长度 -1
            return data; // 返回原队首元素的值
        }
        // 队列长度大于1时
        
        Node h = head;
        head = null; // 原队首元素置为空值
        size --; // 队列长度 -1
        head = h.next; // 队首元素为原队首元素的下一个元素
        tail.next = h.next;  //将尾结点的指针指向原头结点的下一个节点即可
        return h.data; // 返回原队首元素的值
    }

    @Override
    public Object peek() {
        return head == null ? null : head.data; // 队首元素为空的时候返回空值,否则返回队首元素的数据域
    }

测试代码:

 public static void main(String[] args) {
        Queue queue = new LinkedQueue();
        queue.dequeue(); // 在队列为长度为0的时候,尝试出队
        queue.enqueue("3"); // 数据入队
        System.out.println("队列长度为:" + queue.getSize());
        queue.enqueue("4");
        queue.enqueue("5");
        for (int i = 0; i < 100; i ++) {
            queue.enqueue(i); // 数据入队
            queue.dequeue(); // 后面的数据入队,前面的数据出队
            // 这里入队的顺序为 : 3, 4, 5 , 0, ...99
            // 出队的顺序为 : 3, 4, 5, 0 ... 96
        }

        System.out.println("出队元素为: " + queue.dequeue());
        System.out.println("队列长度为: " + queue.getSize());
        System.out.println("队列首元素为: " + queue.peek()); // 获取队列首端元素

    }

测试结果:
在这里插入图片描述
可以看到,结果是符合我们预期的!
从上面的代码中也可以看到,使用循环列表,入队,出队的时间复杂度均为O(1),性能很好!

这样,我们使用数组单向循环链表实现了队列的基本功能!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* * 基于双向链表实现双端队列结构 */ package dsa; public class Deque_DLNode implements Deque { protected DLNode header;//指向头节点(哨兵) protected DLNode trailer;//指向尾节点(哨兵) protected int size;//队列中元素的数目 //构造函数 public Deque_DLNode() { header = new DLNode(); trailer = new DLNode(); header.setNext(trailer); trailer.setPrev(header); size = 0; } //返回队列中元素数目 public int getSize() { return size; } //判断队列是否为空 public boolean isEmpty() { return (0 == size) ? true : false; } //取首元素(但不删除) public Object first() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); return header.getNext().getElem(); } //取末元素(但不删除) public Object last() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); return trailer.getPrev().getElem(); } //在队列前端插入新节点 public void insertFirst(Object obj) { DLNode second = header.getNext(); DLNode first = new DLNode(obj, header, second); second.setPrev(first); header.setNext(first); size++; } //在队列后端插入新节点 public void insertLast(Object obj) { DLNode second = trailer.getPrev(); DLNode first = new DLNode(obj, second, trailer); second.setNext(first); trailer.setPrev(first); size++; } //删除首节点 public Object removeFirst() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); DLNode first = header.getNext(); DLNode second = first.getNext(); Object obj = first.getElem(); header.setNext(second); second.setPrev(header); size--; return(obj); } //删除末节点 public Object removeLast() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); DLNode first = trailer.getPrev(); DLNode second = first.getPrev(); Object obj = first.getElem(); trailer.setPrev(second); second.setNext(trailer); size--; return(obj); } //遍历 public void Traversal() { DLNode p = header.getNext(); while (p != trailer) { System.out.print(p.getElem()+" "); p = p.getNex
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值