《恋上数据结构第1季》队列、双端队列、循环队列、循环双端队列
队列(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季》二叉搜索树BST
二叉搜索树(BinarySearchTree) BST 接口设计 BST 基础 添加元素: add() 删除元素: remove() 删除节点 – 叶子节点 删除节点 – 度为1的节点 删除节点 – ...
- 《恋上数据结构第1季》动态数组实现栈
栈(Stack) 栈的应用 – 浏览器的前进和后退 栈的接口设计 动态数组实现栈 练习题 逆波兰表达式求值 有效的括号 数据结构与算法笔记目录:<恋上数据结构> 笔记目录 想加深 Java ...
- 《恋上数据结构第1季》二叉堆实现优先级队列
优先级队列(Priority Queue) 优先级队列简介 优先队列的底层实现 二叉堆实现优先级队列源码 测试代码 数据结构与算法笔记目录:<恋上数据结构> 笔记目录 想加深 Java 基 ...
- 《恋上数据结构第1季》二叉树代码实现
二叉树(BinaryTree) BinaryTree 基础 遍历(先序.中序.后序.层次遍历) 先序遍历: preorder() 中序遍历: inorder() 后序遍历: postorder() 层 ...
- 《恋上数据结构第1季》红黑树(未完)
红黑树(Red Black Tree) 红黑树介绍 红黑树 与 4阶B树 红黑树 与 2-3-4树 等价转换 红黑树基础代码 完整的红黑树系列代码(恐怖如斯) 二叉树 BinaryTree.java ...
- 《恋上数据结构第1季》动态扩容数组原理及实现
动态扩容数组 什么是数据结构? 线性表 数组(Array) 动态数组(Dynamic Array) 动态数组接口设计 清除所有元素 - clear() 添加元素 - add(E element).ad ...
- 《恋上数据结构第1季》B树
B树 m阶B树的性质 B树 vs 二叉搜索树 搜索 添加 – 上溢 添加 – 上溢的解决(假设5阶) 删除 删除 – 叶子节点 删除 – 非叶子节点 删除 – 下溢 删除 – 下溢的解决 4阶B树 数 ...
- 《恋上数据结构第1季》二叉树基础、真二叉树、满二叉树、完全二叉树、二叉树的遍历(重点)
二叉树(Binary Tree) 树(Tree)的基本概念 有序树.无序树.森林 二叉树(Binary Tree) 二叉树的性质 真二叉树(Proper Binary Tree) 满二叉树(Full ...
- 《恋上数据结构第1季》字典树 Trie
字典树Trie Trie 简介 Trie 实现 接口设计 源码 测试 数据结构与算法笔记目录:<恋上数据结构> 笔记目录 想加深 Java 基础推荐看这个: Java 强化笔记目录 Tri ...
最新文章
- c语言通过域组策略下发软件,windows 2008 server 域环境通过组策略下发计划任务(示例代码)...
- 全国首个5G+AI的智慧交通管理示范应用成功开通!
- 如何利用单片机IO口产生两倍的电源电压
- Android 线程死锁的案例
- mybait 转换失败 null处理
- 记录一下,Sqlite,用GB系列编码排序时是按拼音,UTF-8排序是按笔画
- VC++ 连接SQL Server数据库(远程+本地)
- linux中xjvf指令,linux解压缩命令小结
- 数据科学与python语言——Pandas统计分析基础(时间转换+聚合)
- 架构师必须掌握的 10 条设计原则
- java中线程存活和线程执行的问题!
- java日期函数精确到日,java日期时间函数分享
- [BAT] 执行xcopy命令后出现Invalid num of parameters错误的解决办法
- 7个JavaScript在IE和Firefox浏览器下的差异写法
- CentOS7上使用bind9搭建DNS主从服务器
- jquery文档modal_jQuery代码文档小工具
- Ubuntu 16.04 安装 GCC-7.3.0
- (批处理)如何通过Python或批处理指令删除指定文件夹?
- 什么是云计算?(IaaSPaaS,SaaS区别)
- Linux 之旅 21:编译安装软件
热门文章
- 用集合编写的java通讯录_java使用集合实现通讯录功能
- azure 入门_Azure Cosmos DB中的子文档入门
- sql server 复制_SQL Server复制(合并)–复制架构更改中的性能问题
- ssis行计数变量_SSIS服务性能计数器指南
- noip2019集训测试赛(七)
- Lucas(卢卡斯)定理
- 如何自学并且系统学习计算机网络?(知乎问答)
- sql查询字段值只为汉字(桃)
- Windows下VC++显示UTF-8编码中文
- (转)关于BigDecimal 转化字符串toPlainString()和toString()的区别