队列(Queue)

  • 队列 Queue
    • 队列的接口设计
    • 队列源码
  • 双端队列 Deque
    • 双端队列接口设计
    • 双端队列源码
  • 循环队列 Circle Queue
    • 循环队列实现
    • 索引映射封装
    • 循环队列 – %运算符优化
    • 循环队列测试
  • 循环双端队列 Circle Dequeue
    • 循环双端队列实现
    • 循环双端队列 – %运算符优化
    • 循环双端队列测试
  • 练习
    • 用栈实现队列

数据结构与算法笔记目录:《恋上数据结构》 笔记目录

想加深 Java 基础推荐看这个: Java 强化笔记目录

队列 Queue

队列是一种特殊的线性表,只能在头尾两端进行操作;

  • 队尾(rear):只能从队尾添加元素,一般叫做 enQueue入队
  • 队头(front):只能从队头移除元素,一般叫做 deQueue出队
  • 先进先出的原则,First In First Out,FIFO

队列的接口设计

int size(); // 元素的数量
boolean isEmpty(); // 是否为空
void clear(); // 清空
void enQueue(E element); // 入队
E deQueue(); // 出队
E front(); // 获取队列的头元素

队列的内部实现是否可以直接利用以前学过的数据结构?

  • 动态数组、链表;
  • 优先使用双向链表,因为队列主要是往头尾操作元素;

队列源码

/*** 队列* @author yusael*/
public class Queue <E>{private List<E> list = new LinkedList<>();/*** 入队*/public void enQueue(E element){list.add(element);}/*** 出队*/public E deQueue(){return list.remove(0);}/*** 元素的数量*/public int size(){return list.size();}/*** 清空*/public void clear(){list.clear();}/*** 队头元素*/public E top(){return list.get(0);}/*** 是否为空*/public boolean isEmpty(){return list.isEmpty();}}

双端队列 Deque

双端队列是能在头尾两端添加、删除的队列;

  • 英文 deque 是 double ended queue 的简称;

双端队列接口设计

int size(); // 元素的数量
boolean isEmpty(); // 是否为空
void clear(); // 清空
void enQueueRear(E element); // 从队尾入队
E deQueueFront(); // 从队头出队
void enQueueFront(E element); // 从队头入队
E deQueueRear(); // 从队尾出队
E front(); // 获取队列的头元素
E rear(); // 获取队列的尾元素

双端队列源码

/*** 双端队列* @author yusael*/
public class DeQueue <E> {// 双向链表实现双端队列private List<E> list = new LinkedList<>();/*** 元素的数量*/public int size(){return list.size();}/*** 是否为空*/public boolean isEmpty(){return list.isEmpty();}/*** 清空*/public void clear(){list.clear();}/*** 从队尾入队*/public void enQueueRear(E element){list.add(element);}/*** 从队头入队*/public void enQueueFront(E element){list.add(0, element);}/*** 从队尾出队*/public E deQueueRear(){return list.remove(list.size() - 1);}/*** 从队头出队*/public E deQueueFront(){return list.remove(0);}/*** 获取队列的头元素*/public E front(){return list.get(0);}/*** 获取队里的尾元素*/public E rear(){return list.get(list.size() - 1);}}

循环队列 Circle Queue

其实队列底层也可以使用动态数组实现,并且各项接口也可以优化到 O(1) 的时间复杂度;

  • 这个用数组实现并且优化之后的队列也叫做:循环队列

循环队列实现

/*** 循环队列* @author yusael*/
@SuppressWarnings("unchecked")
public class CircleQueue<E> {private int front; // 队头指针private int size; // 元素数量// 利用动态扩容数组实现的循环队列private E elements[]; // 元素public static final int DEFAULT_CAPACITY = 10; // 初始容量public CircleQueue() {elements = (E[]) new Object[DEFAULT_CAPACITY];}/*** 元素的数量*/public int size() {return size;}/*** 是否为空*/public boolean isEmpty() {return size == 0;}/*** 清空*/public void clear() {for (int i = 0; i < size; i++) {// elements[index(i)] = null;elements[(i + front) %elements.length] = null;}size = 0;front = 0;}/*** 从队头出队*/public E deQueue() {E fronElement = elements[front];elements[front] = null;front = (front + 1) % elements.length;// front = index(1);size--;return fronElement;}/*** 从队尾入队*/public void enQueue(E element) {// 扩容ensureCapacity(size + 1);elements[(front + size) % elements.length] = element;// elements[index(size)] = element;size++;}/*** 获取队列的头元素*/public E front() {return elements[front];}// 扩容private void ensureCapacity(int capacity) {int oldCapacity = elements.length;if (oldCapacity >= capacity)return;// 新容量为旧容量的 1.5 倍int newCapacity = oldCapacity + (oldCapacity >> 1);E[] newElements = (E[]) new Object[newCapacity];for (int i = 0; i < size; i++) { // 旧数组中元素移到新数组newElements[i] = elements[(i + front) % elements.length];// newElements[i] = elements[index(i)];}System.out.println("从" + oldCapacity + "扩容到" + newCapacity);elements = newElements;front = 0; // 重置front}}

索引映射封装

可以发现循环队列中经常有 (front + size) % elements.length 的操作,那是因为如果 front 在队尾了,而又要往后移则会回到开头,该代码就是根据 front 的真实索引计算出在循环队列上的索引。

我们可以将这个封装为一个方法,实际上这个写法使用 % 运算符,性能十分低,后面会对此做优化。

// 将front真实索引转换为循环队列上的索引
private int index(int index) {return (front + index) % elements.length;
}

则循环队列中的其他方法可以修改为如下:

/*** 清空*/
public void clear() {for (int i = 0; i < size; i++) {elements[index(i)] = null;}size = 0;front = 0;
}
/*** 从队头出队*/
public E deQueue() {E fronElement = elements[front];elements[front] = null;front = index(1);size--;return fronElement;
}
/*** 从队尾入队*/
public void enQueue(E element) {// 扩容ensureCapacity(size + 1);elements[index(size)] = element;size++;
}

循环队列 – %运算符优化

尽量避免使用 */%浮点数运算,效率低下;

原理:已知 n >= 0,m > 0:

  • n % m 等价于 n – (m > n ? 0 : m)
    前提条件:n < 2m

由于我们已经封装了索引映射的方法,%运算符优化只需要修改 index 方法即可:

// 将真实索引转换为循环队列上的索引
private int index(int index) {// 10%8 = 2 10-8=2// 10%11 = 10 10index += front;return index - ((index >= elements.length) ? elements.length : 0);
}

完整源码

/*** 循环队列* @author yusael*/
@SuppressWarnings("unchecked")
public class CircleQueue<E> {private int front; // 队头指针private int size; // 元素数量// 利用动态扩容数组实现的循环队列private E elements[]; // 元素public static final int DEFAULT_CAPACITY = 10; // 初始容量public CircleQueue() {elements = (E[]) new Object[DEFAULT_CAPACITY];}/*** 元素的数量*/public int size() {return size;}/*** 是否为空*/public boolean isEmpty() {return size == 0;}/*** 清空*/public void clear() {// while(size >= 0){// elements[(front+size)%elements.length] = null;// size--;// }for (int i = 0; i < size; i++) {elements[index(i)] = null;}size = 0;front = 0;}/*** 从队头出队*/public E deQueue() {E fronElement = elements[front];elements[front] = null;// front = (front + 1) %elements.length;front = index(1);size--;return fronElement;}/*** 从队尾入队*/public void enQueue(E element) {// 扩容ensureCapacity(size + 1);// elements[(front + size) % elements.length] = element;elements[index(size)] = element;size++;}/*** 获取队列的头元素*/public E front() {return elements[front];}// 将真实索引转换为循环队列上的索引private int index(int index) {// 10%8 = 2 10-8=2// 10%11 = 10 10
//      return (front + index) % elements.length;index += front;return index - ((index >= elements.length) ? elements.length : 0);}// 扩容private void ensureCapacity(int capacity) {int oldCapacity = elements.length;if (oldCapacity >= capacity)return;// 新容量为旧容量的 1.5 倍int newCapacity = oldCapacity + (oldCapacity >> 1);E[] newElements = (E[]) new Object[newCapacity];for (int i = 0; i < size; i++) { // 旧数组中元素移到新数组//    newElements[i] = elements[(i + front) % elements.length];newElements[i] = elements[index(i)];}System.out.println("从" + oldCapacity + "扩容到" + newCapacity);elements = newElements;front = 0; // 重置front}@Overridepublic String toString() {StringBuilder string = new StringBuilder();string.append("capcacity=").append(elements.length).append(" size=").append(size).append(" front=").append(front).append(", [");for (int i = 0; i < elements.length; i++) {if (i != 0) {string.append(", ");}string.append(elements[i]);}string.append("]");return string.toString();}}

循环队列测试

public static void main(String[] args) {CircleQueue<Integer> queue = new CircleQueue<Integer>();// 0 1 2 3 4 5 6 7 8 9for (int i = 0; i < 10; i++) {queue.enQueue(i);}// null null null null null 5 6 7 8 9for (int i = 0; i < 5; i++) {queue.deQueue();}// 15 16 17 18 19 f[5] 6 7 8 9for (int i = 15; i < 30; i++) {queue.enQueue(i);}
//      while (!queue.isEmpty()) {//          System.out.println(queue.deQueue());
//      }
//      queue.clear();System.out.println(queue);
}
从10扩容到15
从15扩容到22
capcacity=22 size=20 front=0, [5, 6, 7, 8, 9, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, null, null]

循环双端队列 Circle Dequeue

循环双端队列:可以进行两端添加、删除操作循环队列

循环队列中用了 front 指针来表示队列的头部,双端循环队列是否需要再使用一个 rear 指针来表示队列的尾部?

  • 不需要,只要有了头指针便可以算出尾部

首先理解一下循环双端队列中索引封装映射

  • 传入的 index 是相对于 front 的索引,返回的是真实的索引:
    比如要获得 头部指针 的前一位,则是 index(-1)(用于队头入队)
    比如要获得 尾部指针,则是 index(size - 1)
private int index(int index) {index += front;if (index < 0) { // index 为负数return index + elements.length;}// index 为正数return index % elements.length;
}

循环双端队列实现

package com.mj.circle;/*** 循环双端队列* @author yusael*/
@SuppressWarnings("unchecked")
public class CircleDeque<E> {private int front; // 队头指针private int size; // 元素数量private E elements[]; // 元素public static final int DEFAULT_CAPACITY = 10; // 初始容量public CircleDeque() {elements = (E[]) new Object[DEFAULT_CAPACITY];}/*** 元素的数量*/public int size() {return size;}/*** 是否为空*/public boolean isEmpty() {return size == 0;}/*** 清空*/public void clear() {for (int i = 0; i < size; i++) {elements[index(i)] = null;}front = 0;size = 0;}/*** 从队尾入队*/public void enQueueRear(E element) {// 头 1 r(2) null null null f(6) 7 8 9 尾ensureCapacity(size + 1);elements[index(size)] = element;size++;}/*** 从队头入队*/public void enQueueFront(E element) {ensureCapacity(size + 1);front = index(-1);elements[front] = element;size++;}/*** 从队尾出队*/public E deQueueRear() {int rearIndex = index(size - 1);E rear = elements[rearIndex];elements[rearIndex] = null;size--;return rear;}/*** 从队头出队*/// 头 1 r(2) null null f(5) 6 7 8 9 尾public E deQueueFront() {E frontElement = elements[front];elements[front] = null;front = index(1);size--;return frontElement;}/*** 获取队列的头元素*/public E front() {return elements[front];}/*** 获取队列的尾元素*/public E rear() {return elements[index(size - 1)];}// 索引封装映射private int index(int index) {index += front;if (index < 0) { // index 为负数return index + elements.length;}// index 为正数return index % elements.length;}// 数组扩容private void ensureCapacity(int capacity) {int oldCapacity = elements.length;if (oldCapacity >= capacity)return;int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容为1.5倍E newElements[] = (E[]) new Object[newCapacity];for (int i = 0; i < size; i++) {newElements[i] = elements[index(i)];}elements = newElements;front = 0; // 重置front}
}

循环双端队列 – %运算符优化

尽量避免使用 */%浮点数运算,效率低下;

原理:已知 n >= 0,m > 0:

  • n % m 等价于 n – (m > n ? 0 : m)
    前提条件:n < 2m

由于我们已经封装了索引映射的方法,%运算符优化只需要修改 index 方法即可:

// 索引封装映射
private int index(int index) {index += front;if (index < 0) { // index 为负数return index + elements.length;}// index 为正数return index % elements.length;
}

完整源码

package com.mj.circle;/*** 循环双端队列* @author yusael*/
@SuppressWarnings("unchecked")
public class CircleDeque <E> {    private int front; // 队头指针private int size;  // 元素数量private E elements[]; // 元素public static final int DEFAULT_CAPACITY = 10; // 初始容量public CircleDeque() {elements = (E[]) new Object[DEFAULT_CAPACITY];}/*** 元素的数量*/public int size(){return size;}/*** 是否为空*/public boolean isEmpty(){return size == 0;}/*** 清空*/public void clear(){for(int i = 0; i < size; i++){elements[index(i)] = null;}front = 0;size = 0;}/*** 从队尾入队*/public void enQueueRear(E element){// 头 1 r(2) null null null f(6) 7 8 9  尾ensureCapacity(size + 1);elements[index(size)] = element;size++;}/*** 从队头入队*/public void enQueueFront(E element){ensureCapacity(size + 1);/*if(front - 1 < 0){front += elements.length;}front = front - 1;elements[front-1] = element;*/front = index(-1);elements[front] = element;size++;}/*** 从队尾出队*/public E deQueueRear(){E rearElement = elements[(front+size-1)%elements.length];elements[(front+size-1)%elements.length] = null;size--;return rearElement;}/*** 从队头出队*/// 头 1 r(2) null null f(5) 6 7 8 9  尾public E deQueueFront() {E frontElement = elements[front];elements[front] = null;front = index(1);size--;return frontElement;}/*** 获取队列的头元素*/public E front(){return elements[front];}/*** 获取队列的尾元素*/public E rear(){return elements[index(size - 1)];}// 索引封装映射private int index(int index) {index += front;if (index < 0) {return index + elements.length;}return index - ((index >= elements.length) ? elements.length : 0);}// 数组扩容private void ensureCapacity(int capacity){int oldCapacity = elements.length;if(oldCapacity >= capacity) return;int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容为1.5倍E newElements[] = (E[]) new Object[newCapacity];for(int i = 0; i < size; i++){newElements[i] = elements[index(i)];}elements = newElements;front = 0; // 重置front}@Overridepublic String toString() {StringBuilder string = new StringBuilder();string.append("capcacity=").append(elements.length).append(" size=").append(size).append(" front=").append(front).append(", [");for (int i = 0; i < elements.length; i++) {if (i != 0) {string.append(", ");}string.append(elements[i]);}string.append("]");return string.toString();}
}

循环双端队列测试

public static void main(String[] args) {CircleDeque<Integer> queue = new CircleDeque<>();// 头5 4 3 2 1  100 101 102 103 104 105 106 8 7 6 尾// 头 8 7 6  5 4 3 2 1  100 101 102 103 104 105 106 107 108 109 null null 10 9 尾for (int i = 0; i < 10; i++) {queue.enQueueFront(i + 1);queue.enQueueRear(i + 100);}// 头 null 7 6  5 4 3 2 1  100 101 102 103 104 105 106 null null null null null null null 尾for (int i = 0; i < 3; i++) {queue.deQueueFront();queue.deQueueRear();}// 头 11 7 6  5 4 3 2 1  100 101 102 103 104 105 106 null null null null null null 12 尾queue.enQueueFront(11);queue.enQueueFront(12);System.out.println(queue);
//      while (!queue.isEmpty()) {//          System.out.println(queue.deQueueFront());
//      }
}
capcacity=22 size=16 front=21, [11, 7, 6, 5, 4, 3, 2, 1, 100, 101, 102, 103, 104, 105, 106, null, null, null, null, null, null, 12]

练习

用栈实现队列

232_用栈实现队列:https://leetcode-cn.com/problems/implement-queue-using-stacks/

准备2个栈:inStack、outStack

  • 入队时,push 到 inStack 中
  • 出队时
    如果 outStack 为空,将 inStack 所有元素逐一弹出,push 到 outStack,outStack 弹出栈顶元素
    如果 outStack 不为空, outStack 弹出栈顶元素
/*** Your MyQueue object will be instantiated and called as such:* MyQueue obj = new MyQueue();* obj.push(x);* int param_2 = obj.pop();* int param_3 = obj.peek();* boolean param_4 = obj.empty();*/public class MyQueue {private Stack<Integer> inStack = new Stack<>();private Stack<Integer> outStack = new Stack<>();/** Initialize your data structure here. */public MyQueue() {}/** Push element x to the back of queue. */public void push(int x) {inStack.push(x);}/** Removes the element from in front of queue and returns that element. */public int pop() {if(outStack.isEmpty()){ // 右栈为空,则先全部放进右栈while(!inStack.isEmpty()){outStack.push(inStack.pop());}}return outStack.pop();}/** Get the front element. */public int peek() {if(outStack.isEmpty()){ // 右栈为空,则先全部放进右栈while(!inStack.isEmpty()){outStack.push(inStack.pop());}}return outStack.peek();}/** Returns whether the queue is empty. */public boolean empty() {return inStack.isEmpty() && outStack.isEmpty();}
}

《恋上数据结构第1季》队列、双端队列、循环队列、循环双端队列相关推荐

  1. 《恋上数据结构第1季》二叉搜索树BST

    二叉搜索树(BinarySearchTree) BST 接口设计 BST 基础 添加元素: add() 删除元素: remove() 删除节点 – 叶子节点 删除节点 – 度为1的节点 删除节点 – ...

  2. 《恋上数据结构第1季》动态数组实现栈

    栈(Stack) 栈的应用 – 浏览器的前进和后退 栈的接口设计 动态数组实现栈 练习题 逆波兰表达式求值 有效的括号 数据结构与算法笔记目录:<恋上数据结构> 笔记目录 想加深 Java ...

  3. 《恋上数据结构第1季》二叉堆实现优先级队列

    优先级队列(Priority Queue) 优先级队列简介 优先队列的底层实现 二叉堆实现优先级队列源码 测试代码 数据结构与算法笔记目录:<恋上数据结构> 笔记目录 想加深 Java 基 ...

  4. 《恋上数据结构第1季》二叉树代码实现

    二叉树(BinaryTree) BinaryTree 基础 遍历(先序.中序.后序.层次遍历) 先序遍历: preorder() 中序遍历: inorder() 后序遍历: postorder() 层 ...

  5. 《恋上数据结构第1季》红黑树(未完)

    红黑树(Red Black Tree) 红黑树介绍 红黑树 与 4阶B树 红黑树 与 2-3-4树 等价转换 红黑树基础代码 完整的红黑树系列代码(恐怖如斯) 二叉树 BinaryTree.java ...

  6. 《恋上数据结构第1季》动态扩容数组原理及实现

    动态扩容数组 什么是数据结构? 线性表 数组(Array) 动态数组(Dynamic Array) 动态数组接口设计 清除所有元素 - clear() 添加元素 - add(E element).ad ...

  7. 《恋上数据结构第1季》B树

    B树 m阶B树的性质 B树 vs 二叉搜索树 搜索 添加 – 上溢 添加 – 上溢的解决(假设5阶) 删除 删除 – 叶子节点 删除 – 非叶子节点 删除 – 下溢 删除 – 下溢的解决 4阶B树 数 ...

  8. 《恋上数据结构第1季》二叉树基础、真二叉树、满二叉树、完全二叉树、二叉树的遍历(重点)

    二叉树(Binary Tree) 树(Tree)的基本概念 有序树.无序树.森林 二叉树(Binary Tree) 二叉树的性质 真二叉树(Proper Binary Tree) 满二叉树(Full ...

  9. 《恋上数据结构第1季》字典树 Trie

    字典树Trie Trie 简介 Trie 实现 接口设计 源码 测试 数据结构与算法笔记目录:<恋上数据结构> 笔记目录 想加深 Java 基础推荐看这个: Java 强化笔记目录 Tri ...

最新文章

  1. c语言通过域组策略下发软件,windows 2008 server 域环境通过组策略下发计划任务(示例代码)...
  2. 全国首个5G+AI的智慧交通管理示范应用成功开通!
  3. 如何利用单片机IO口产生两倍的电源电压
  4. Android 线程死锁的案例
  5. mybait 转换失败 null处理
  6. 记录一下,Sqlite,用GB系列编码排序时是按拼音,UTF-8排序是按笔画
  7. VC++ 连接SQL Server数据库(远程+本地)
  8. linux中xjvf指令,linux解压缩命令小结
  9. 数据科学与python语言——Pandas统计分析基础(时间转换+聚合)
  10. 架构师必须掌握的 10 条设计原则
  11. java中线程存活和线程执行的问题!
  12. java日期函数精确到日,java日期时间函数分享
  13. [BAT] 执行xcopy命令后出现Invalid num of parameters错误的解决办法
  14. 7个JavaScript在IE和Firefox浏览器下的差异写法
  15. CentOS7上使用bind9搭建DNS主从服务器
  16. jquery文档modal_jQuery代码文档小工具
  17. Ubuntu 16.04 安装 GCC-7.3.0
  18. (批处理)如何通过Python或批处理指令删除指定文件夹?
  19. 什么是云计算?(IaaSPaaS,SaaS区别)
  20. Linux 之旅 21:编译安装软件

热门文章

  1. 用集合编写的java通讯录_java使用集合实现通讯录功能
  2. azure 入门_Azure Cosmos DB中的子文档入门
  3. sql server 复制_SQL Server复制(合并)–复制架构更改中的性能问题
  4. ssis行计数变量_SSIS服务性能计数器指南
  5. noip2019集训测试赛(七)
  6. Lucas(卢卡斯)定理
  7. 如何自学并且系统学习计算机网络?(知乎问答)
  8. sql查询字段值只为汉字(桃)
  9. Windows下VC++显示UTF-8编码中文
  10. (转)关于BigDecimal 转化字符串toPlainString()和toString()的区别