队列及其实现
来源:互联网 发布:数据库exists 编辑:程序博客网 时间:2024/06/06 11:36
和栈相反,队列(Queue)是一种先进先出(First In First Out,缩写为FIFO)的线性表。它只允许在表的一端进行插入,而在另一端进行删除元素。这和我们日常生活中的排队是一致的,最早进入队列的元素最早离开。
在队列中,允许插入的一端叫做队尾(rear),允许删除的一端则成为队头(front)。向队尾插入元素称为进队或入队,新元素入队后成为新的队尾元素;从队头删除元素称为离队或出队,其后续元素成为新的队首元素。
1.队列的顺序存储与实现
在队列的顺序存储中,我们可以将队列当作一般的数组加以实现,但这样做的效果并不好。尽管我们可以用一个指针rear来指示队尾,使得enqueue
运算可以在O(1)时间内完成,但是在执行dequeue
时,为了删除队首元素,必须将数组中其他所有元素都向前移动一个位置,这样,当队列中有n个元素时,dequeue
就需要O(n)时间。
为此呢,我们也可以考虑增加一个front指针来指向队首的位置,这样的话删除队首元素只需要将front指针后移即可,如下图所示:
但是这样又有一个问题,就是当rear指针移动到数组最后的时候,这时候队列并没有满,数组的前面还有空余的空间,所以也不宜进行存储再分配来扩大数组空间。
一个比较巧妙的办法是将顺序队列臆造为一个环状的空间,即数组A[0..capacity-1]中的单元不是排成一排,而是围成一个圆环,即A[0]接在A[capacity-1]后面。如下图所示:
上面的数组,我们可以称之为循环数组,而用循环数组实现的队列称为循环队列。我们用front指针指向队首元素所在的单元,用rear指针指向队尾元素所在单元的下一个单元。如上图所示,队首元素存储在数组下标为0的位置,front=0,队尾元素存储在数组下标为2的位置,rear=3。
但是,这里又有一个问题,即如何表示满队列和空队列。
举例来说,如下图(b)所示,在该循环队列中,队首元素为e0,队尾元素为e3。当e4、e5、e6、e7相继入队列后,如图(c)所示,队列空间被占满,此时队尾指针rear追上队首指针front,即有rear=front。反之呢,如果从图(b)所示的状态开始,e0、e1、e2、e3相继出队,则得到空队列,此时队首指针front追上队尾指针rear,所以也有front=rear。
可见,仅凭front=rear是无法判断队列状态是“空”还是“满”的。
解决这个问题有两个处理思路,一种即增加一个变量size,用它表示队列中的元素的个数,通过size的大小即可判断队列是否为满或者空。第二种思路就是少用一个存储单元,当队尾指针的下一个单元就是队首指针所指单元时,则队列已满,停止入队。这样队尾指针就不会追上队首指针,而队列满时就不会有front=rear了,而是需要满足(rear+1) % capacity = front
。而队列判空的条件不变,仍是front=rear
。
下述代码基于第二种思路,利用循环数组实现了循环队列:
public class ArrayQueue<T> { private final int DEFAULT_CAPACITY = 8; // 默认容量 private T[] elements;// 数组 private int front;// 队头指针 private int rear; // 队尾指针 @SuppressWarnings("unchecked") public ArrayQueue() { elements = (T[]) new Object[DEFAULT_CAPACITY]; front = 0; rear = 0; } /** * 队列的大小,即队列中元素的个数 * * @return */ public int size() { // 注意负数,-1 % 8 = -1 而不是 7,所以这里要加上elements.length return (rear - front + elements.length) % elements.length; } /** * 判空 * * @return */ public boolean isEmpty() { return front == rear; } /** * 判断队列是否满 * * @return */ private boolean isFull() { return (rear + 1) % elements.length == front; } /** * 入队 * * @param e */ public void enqueue(T e) { if (isFull()) { ensureCapacity(); } elements[rear] = e; rear = (rear + 1) % elements.length; } /** * 扩充容量 */ @SuppressWarnings("unchecked") private void ensureCapacity() { // 新的数组 T[] temp = (T[]) new Object[elements.length * 2 + 1]; int size = size(); for (int i = 0; i < size; i++) { temp[i] = elements[front]; front = (front + 1) % elements.length; } front = 0; rear = size; elements = temp; } /** * 出队 * * @return */ public T dequeue() { if (isEmpty()) { // 暂时先抛出这个异常 throw new RuntimeException(); } T e = elements[front]; front = (front + 1) % elements.length; return e; }}
2.队列的链式存储与实现
队列的链式存储使用单链表来实现。为了方便,这里使用带头结点的单链表。
根据单链表的特点,我们可以选择链表的头部作为队首,尾部作为队尾。
public class LinkedQueue<T> { class Node { private T data;// 数据域 private Node next;// 指针域 public Node() { this(null, null); } public Node(T data, LinkedQueue<T>.Node next) { super(); this.data = data; this.next = next; } public T getData() { return data; } public void setData(T data) { this.data = data; } public Node getNext() { return next; } public void setNext(Node next) { this.next = next; } } private Node front; // 队头指针 private Node rear; // 队尾指针 private int size; // 元素个数 public LinkedQueue() { front = new Node(); rear = front; size = 0; } /** * 判空 * * @return */ public boolean isEmpty() { return size == 0; } /** * 队列的大小,即元素的个数 * * @return */ public int size() { return size; } /** * 入队 * * @param e */ public void enqueue(T e) { Node newNode = new Node(e, null); rear.setNext(newNode); rear = newNode; size++; } public T dequeue() { if (isEmpty()) { // 暂时抛出这个异常 throw new RuntimeException(); } front = front.getNext(); size--; return front.getData(); }}
代码使用了size这个成员变量来指示队列的大小。这样,所有的操作都可以在O(1)时间内完成。
- 队列---顺序队列及其实现
- 队列---链式队列及其实现
- 队列-循环队列及其实现
- 队列及其实现
- 队列及其c语言实现
- 队列及其c语言实现
- 基本数据结构-队列的实现及其运用
- 队列(Queue)的python实现及其应用
- 消息队列总结及其实现代码
- 循环顺序队列介绍及其C++实现
- 链队列介绍及其C++实现
- 链队列的C++实现及其应用
- 队列的基本操作实现及其应用
- php 实现队列和双向队列及其用例
- 队列的顺序存储结构及其基本运算的实现
- 数据结构队列及其用法(C语言实现)
- 优先队列的实现及其在哈夫曼编码中的应用
- 队列的顺序存储结构及其基本运算的实现
- LeetCode之Reverse Words in a String III
- Insertion Sort List
- 阻塞与非阻塞,同步与异步
- 无人驾驶中的决策规划控制技术
- maven的jar文件配置
- 队列及其实现
- Spinner控件的学习
- markdownppad使用记录
- DeepID训练过程中loss居高不下解决方法
- 浅谈前端跨域的几种解决方式
- Oracle锁表、解锁、批量解锁表
- 脚本编程
- 提权系列(一)----Windows Service 服务器提权初识与exp提权,mysql提权
- java笔记整理(3)