我的《恋上数据结构》源码(第1季 + 第2季):https://github.com/szluyu99/Data_Structure_Note
队列 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 10
index += 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
}
@Override
public 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 9
for (int i = 0; i < 10; i++) {
queue.enQueue(i);
}
// null null null null null 5 6 7 8 9
for (int i = 0; i < 5; i++) {
queue.deQueue();
}
// 15 16 17 18 19 f[5] 6 7 8 9
for (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
}
@Override
public 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();
}
}