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

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

想加深 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 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();
    }
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萌宅鹿同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值