【数据结构与算法】之队列

一、什么是队列

前言:双端队列先不在此赘述。

(一)队列的定义

队列 (queue) 是只允许在一端(队头 front)进行插入(enqueue)操作,而在另一端(队尾 rear)进行删除(dequeue)操作的线性表。

(二)队列的特点

  • 遵循先进先出 (FIFO) 的原则。
  • 队列只允许在队尾进行插入,在队头进行删除。
  • 一般的,队列的插入操作称为入队 (enqueue),队列的删除操作称为出队 (dequeue)。

二、为什么要用队列

许多应用场景都使用了队列的结构。作为三大基本数据结构之一,我们必须掌握。

三、如何操作队列

队列作为一种抽象数据类型 (ADT),可通过数组链表来实现。
首先提供的是 Queue 接口,其内部定义了队列的抽象方法。

/**
 * 队列接口,遵循先进先出的原则。
 *
 * @author likezhen
 * @version 1.0
 */
public interface Queue<E> {
    /**
     * 返回队列中存放的数据个数
     *
     * @return 队列中存放的数据个数
     */
    int size();

    /**
     * 判断是否为空队列
     *
     * @return 是否为空队列
     */
    boolean isEmpty();

    /**
     * 将数据插入队尾 (rear)
     * @param e 待入队的数据
     */
    void enqueue(E e);

    /**
     * 移除并返回队头数据
     *
     * @return 队头数据(如果为空队列则返回Null)
     */
    E dequeue();

    /**
     * 返回队头数据
     *
     * @return 队头数据(如果为空队列则返回Null)
     */
    E peek();
}

(一)用循环数组实现队列

循环数组

图一、用数组实现的队列的结构

ArrayQueue 类实现队列接口。因为用数组作为储存容器,所以需要先指明其容量,一旦确定,就不能更改。特别地,在此数组队列中,我们选择记录队头指针 front 和数据个数 size的办法(也可以选择记录队头指针 front 和队尾指针 rear的办法)。实现循环数组的关键在于 % 取余操作。

/**
 * 通过数组来实现队列结构
 * 提供了 enqueue(), dequeue(), peek()等方法
 *
 * @author likezhen
 * @version 1.0
 */
class ArrayQueue<E> implements Queue<E> {
    private E[] data;
    private int front; //指向队头的指针
    private int size; //数据的个数
    private static final int CAPACITY = 10; //默认队列的初始容量为10

    public ArrayQueue() {
        this(CAPACITY);
    }

    public ArrayQueue(int capacity) { //可自定义队列的容量
        data = (E[]) new Object[capacity];
    }

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

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

    @Override
    public void enqueue(E e) throws IllegalStateException {
        if (size == data.length) throw new IllegalStateException("队列已满");
        int avail = (front + size) % data.length; //实现循环数组的关键操作
        data[avail] = e;
        size++;
    }

    @Override
    public E dequeue() {
        if (isEmpty()) return null;
        E answer = data[front];
        data[front] = null;
        front = (front + 1) % data.length; //实现循环数组的关键操作
        size--;
        return answer;
    }

    @Override
    public E peek() {
        if (isEmpty()) return null;
        return data[front];
    }
}

测试类中进行了三次入队操作,查看队头元素,后通过遍历拿到出队顺序。

/**
 * 用数组实现队列的测试类
 *
 * @author likezhen
 * @version 1.0
 */
public class ArrayQueueApp {
    public static void main(String[] args) {
        ArrayQueue<Person> arrayQueue = new ArrayQueue<>(3); //空队列,容量为3
        arrayQueue.enqueue(new Person("A", 1)); //入队
        arrayQueue.enqueue(new Person("B", 2)); //入队
        arrayQueue.enqueue(new Person("C", 3)); //入队
        System.out.println("--------入队顺序-------\n" +
                "Person{name='A', age=1}\n" +
                "Person{name='B', age=2}\n" +
                "Person{name='C', age=3}");
        System.out.println("--------队头元素--------");
        System.out.println(arrayQueue.peek());
        System.out.println("--------出队顺序--------");
        while (!arrayQueue.isEmpty()) {
            System.out.println(arrayQueue.peek());
            arrayQueue.dequeue();
        }
    }
}

测试结果:

D:\Java\jdk\bin\java.exe 
--------入队顺序-------
Person{name='A', age=1}
Person{name='B', age=2}
Person{name='C', age=3}
--------队头元素--------
Person{name='A', age=1}
--------出队顺序--------
Person{name='A', age=1}
Person{name='B', age=2}
Person{name='C', age=3}

Process finished with exit code 0

(二)用单链表实现队列

LinkedQueue 类实现了队列接口,封装了一个单链表对象。通过调用单链表的各种方法,即可实现链栈的操作。关于单链表的源代码,可参考我之前总结的一篇关于链表的文章——【数据结构与算法】之链表
单链表实现的队列

图二、单链表实现的队列的结构

队列方法单链表方法描述
size()list.getSize()获取队列(或链表)中的数据个数
isEmpty()list.isEmpty()判断是否为空队列(或空链表)
enqueue(E e)list.addLast(E e)在队尾(或表尾)添加数据
dequeue()list.removeFirst()移除队头(或表头)的数据
peek()list.first()获取队头(或表头)存放的数据
表一、队列中与单链表中的对应方法

/**
 * 通过链表来实现队列结构
 * 提供了 enqueue(), dequeue(), peek()等方法
 *
 * @author likezhen
 * @version 1.0
 */
class LinkedQueue<E> implements Queue<E> {
    private SinglyLinkedList<E> list = new SinglyLinkedList<>();

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

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public void enqueue(E e) {
        list.addLast(e);
    }

    @Override
    public E dequeue() {
        return list.removeFirst();
    }

    @Override
    public E peek() {
        return list.first();
    }
}

入队顺序为 A->B->C,队头数据为A,出队顺序为 A->B->C,符合先进先出的原则。

/**
 * 用链表实现队列的测试类
 *
 * @author likezhen
 * @version 1.0
 */
public class LinkedQueueApp {
    public static void main(String[] args) {
        LinkedQueue<Person> linkedQueue = new LinkedQueue<>();
        linkedQueue.enqueue(new Person("A", 1)); //入队
        linkedQueue.enqueue(new Person("B", 2)); //入队
        linkedQueue.enqueue(new Person("C", 3)); //入队
        System.out.println("--------入队顺序-------\n" +
                "Person{name='A', age=1}\n" +
                "Person{name='B', age=2}\n" +
                "Person{name='C', age=3}");
        System.out.println("--------队头元素--------");
        System.out.println(linkedQueue.peek());
        System.out.println("--------出队顺序--------");
        while (!linkedQueue.isEmpty()) {
            System.out.println(linkedQueue.peek());
            linkedQueue.dequeue();
        }
    }
}

测试结果:

D:\Java\jdk\bin\java.exe
--------入队顺序-------
Person{name='A', age=1}
Person{name='B', age=2}
Person{name='C', age=3}
--------队头元素--------
Person{name='A', age=1}
--------出队顺序--------
Person{name='A', age=1}
Person{name='B', age=2}
Person{name='C', age=3}

Process finished with exit code 0

时间复杂度分析:
队列和栈类似,因为其特殊的结构,所以其入队出队等所有队列操作的(最坏)时间复杂度都为 O(1),与数据量无关。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值