文章目录
- 一.什么是队列
- 二.队列的使用
- 2.1 队列的基本操作
- 2.2 队列的基本使用
- 三.队列的模拟实现
- 3.1 数组实现队列
- 3.2 链表实现队列
- 四.队列的应用
- 4.1 设计循环队列
- 4.2 设计双端队列
- 4.3 队列实现栈
- 4.4 栈实现队列
- 五.总结
一.什么是队列
- 队列是一种先入先出(FIFO)的线性表数据结构
- 添加和删除操作只在表的两端进行,一端为队头,另一端为队尾
- 添加操作在队尾进行,称为入队或进队,删除操作在队头进行,称为出队
二.队列的使用
2.1 队列的基本操作
队列的图示
2.2 队列的基本使用
java内部的api
public static void main(String[] args) {Queue<Integer> q = new LinkedList<>();q.offer(1);q.offer(2);q.offer(3);q.offer(4);q.offer(5); // 从队尾入队列System.out.println(q.size());System.out.println(q.peek()); // 获取队头元素q.poll();System.out.println(q.poll()); // 从队头出队列,并将删除的元素返回if(q.isEmpty()){System.out.println("队列空");}else{System.out.println(q.size());}}
三.队列的模拟实现
- 可以使用数组或链表实现队列
- 使用数组实现时,需要维护两个指针front和rear,分别指向队头和队尾的下一个位置
- 使用链表实现时,链表的头节点作为队头,尾节点作为队尾
- 实现需要包含的方法有:入队add、出队remove、获取队头peek、判断是否为空isEmpty等
3.1 数组实现队列
public class ArrayQueue {private int front;private int rear;private int[] arr;private int capacity;public ArrayQueue(int capacity) {this.capacity = capacity;front = rear = 0;arr = new int[capacity];}// 入队操作,将元素加入队尾public void add(int elem) {if (rear == capacity) {System.out.println("队列已满");return;}arr[rear] = elem;rear++;}// 出队操作,移除队头元素public int remove() {if (front == rear) {System.out.println("队列为空"); return -1;}int elem = arr[front];front++;return elem;} // 获取队头元素public int peek() {if (front == rear) {System.out.println("队列为空");return -1;}return arr[front];} // 判断队列是否为空public boolean isEmpty() {return front == rear;}
}
3.2 链表实现队列
/*** @Author 12629* @Description:*/
public class MyQueue {static class Node {public int val;public Node next;public Node(int val) {this.val = val;}}public Node head;public Node last;public int usedSize;//入队public void offer(int val) {Node node = new Node(val);if(head == null) {head = node;last = node;}else {last.next = node;last = node;}usedSize++;}public int poll() {if(empty()) {throw new EmptyException("队列为空");}int ret = head.val;head = head.next;if(head == null) {last = null;//只有一个节点 那么last也要置空}usedSize--;return ret;}public boolean empty() {return usedSize == 0;}public int peek() {if(empty()) {throw new EmptyException("队列为空");}return head.val;}public int getUsedSize() {return usedSize;}
}
四.队列的应用
4.1 设计循环队列
其实我们在设计循环队列的时候,我们最重要的一点就是如何考虑空与满的情况
大家肯定很难理解我在说什么,大家看我接下来的操作.
我们只要解决上面俩个核心问题,就能完整的构造循环队列
这思路巧妙的应用了一个取模运算
当然这里我们提供了三种思路:
- 通过添加 size 属性记录
public boolean enQueue(int value) {if (size == elem.length) return false; //判断满elem[rear] = value;rear = (rear + 1) % elem.length;size++;return true;}public boolean deQueue() {if (size == 0) return false; //判断空front = (front + 1) % elem.length;size--;return true;}
- 保留一个位置
public class MyCircularQueue {private int[] elem;private int front;private int rear;public MyCircularQueue(int k) {elem = new int[k+1]; //多一位}public boolean enQueue(int value) {if ((rear + 1) % elem.length == front) return false; //判断满elem[rear] = value;rear = (rear + 1) % elem.length; return true;}
}
- 使用标记
public boolean enQueue(int value) {if (full) return false; //判断满elem[rear] = value;rear = (rear + 1) % elem.length; if (rear == front) full = true; //修改标记return true;}public boolean deQueue() {if (isEmpty()) return false; //判断空front = (front + 1) % elem.length;full = false; //修改标记return true;}
具体步骤:
5. 使用数组elem存储队列元素,定义front和rear指针表示队头和队尾位置。
6. enQueue(value)方法:先判断队列是否已满,未满则将元素加入rear位置,rear加1取模防止越界。
7. deQueue()方法:先判断队列是否为空,非空则front加1取模。
8. Front()方法:直接返回front位置元素,队空则返回-1。
9. Rear()方法:直接返回rear-1位置元素,队空则返回-1。需要判断rear是否为0,是则返回length-1位置元素。
10. isEmpty()方法:通过判断front和rear是否相等确定队列是否为空。
11. isFull()方法:通过判断rear+1位置是否等于front确定队列是否已满。
时间复杂度分析:
- enQueue和deQueue方法时间复杂度O(1)。
- 其他方法时间复杂度O(1)。
空间复杂度分析:O(n),数组使用O(n)空间。
具体代码:
class MyCircularQueue {private int[] elem;private int front;//表示队列的头private int rear;//表示队列的尾public MyCircularQueue(int k) {//如果是浪费空间 这里必须处理多加一个1this.elem = new int[k+1];}/*** 入队列* @param value* @return*/public boolean enQueue(int value) {//1、检查是否队列是满的if(isFull()){return false;}//2、elem[rear] = value;//rear++;rear = (rear+1) % elem.length;return true;}/*** 出队列* @return*/public boolean deQueue() {if(isEmpty()) {return false;}//front++;front = (front+1) % elem.length;return true;}/*** 得到队头元素* @return*/public int Front() {if(isEmpty()) {return -1;}return elem[front];}/*** 得到队尾元素* @return*/public int Rear() {if(isEmpty()) {return -1;}int index = (rear == 0) ? elem.length-1 : rear-1;return elem[index];}public boolean isEmpty() {return front == rear;}/*** 队列是否为满* @return*/public boolean isFull() {/* if( (rear+1) % elem.length == front) {return true;}return false;*/return (rear+1) % elem.length == front;}
}
4.2 设计双端队列
具体思路:
- 可以使用链表或数组实现,这里我们使用数组实现。定义数组elem存储数据,front和rear分别表示头尾指针。
- 添加方法:
- addFront(val):将元素插入至队头,front减1取模,将val放入front位置。
- addRear(val):将元素插入至队尾,rear加1取模,将val放入rear位置。
- 移除方法:
- removeFront():移除队头元素,front加1取模,返回front位置元素。
- removeRear():移除队尾元素,rear减1取模,返回rear位置元素。
- 获取方法:
- getFront():返回front位置元素,队空则返回-1。
- getRear():返回rear位置元素,队空则返回-1。
- 判断方法:
- isEmpty():当front==rear时,队列为空,返回true,否则返回false。
- isFull():当(rear+1)%len==front时,队列已满,返回true,否则返回false。len为数组长度。
- 扩容方法:当添加元素时判断队列已满,调用扩容方法expand将数组size*2,并把原数据复制过来。
具体代码:
public class Deque {private int[] elem;private int front;private int rear;private int len;public Deque(int capacity) {elem = new int[capacity];front = rear = 0;len = 0;}//在队头添加元素public void addFront(int val) {if (isFull()) expand();front = (front - 1 + elem.length) % elem.length;elem[front] = val;len++;}//在队尾添加元素public void addRear(int val) {if (isFull()) expand();elem[rear] = val;rear = (rear + 1) % elem.length; len++;} //移除队头元素public int removeFront() {if (isEmpty()) return -1;int ret = elem[front];front = (front + 1) % elem.length;len--;return ret;}//移除队尾元素public int removeRear() {if (isEmpty()) return -1;rear = (rear - 1 + elem.length) % elem.length;int ret = elem[rear];len--;return ret;}//获取队头元素public int getFront() {if (isEmpty()) return -1;return elem[front];} //获取队尾元素public int getRear() {if (isEmpty()) return -1;return elem[(rear - 1 + elem.length) % elem.length];} //判断队列是否为空public boolean isEmpty() {return front == rear; }//判断队列是否已满public boolean isFull() {return (rear + 1) % elem.length == front; }//扩容方法private void expand() {int[] newElem = new int[elem.length * 2];for (int i = 0; i < len; i++) {newElem[i] = elem[(i + front) % elem.length]; }front = 0;rear = len;elem = newElem;}
}
4.3 队列实现栈
队列实现栈,在实现栈之前,我们先了解一下栈是怎么工作的,看下图
再看看两个队列是怎么实现栈的过程,我们用队列模拟,要记住一个核心规则
我演示一下入栈的规则
出栈的模拟演示:
代码如下:
import java.util.LinkedList;
import java.util.Queue;class MyStack {private Queue<Integer> qu1;private Queue<Integer> qu2;public MyStack() {qu1 = new LinkedList<>();qu2 = new LinkedList<>();}public void push(int x) {if(!qu1.isEmpty()) {qu1.offer(x);} else if (!qu2.isEmpty()) {qu2.offer(x);}else {qu1.offer(x);}}public int pop() {if(empty()) {return -1;//两个队列都为空,意味着当前的栈为空}if(!qu1.isEmpty()) {int size = qu1.size();for (int i = 0; i < size-1; i++) {//for (int i = 0; i < qu1.size()-1; i++) {int val = qu1.poll();qu2.offer(val);}return qu1.poll();}else {int size = qu2.size();for (int i = 0; i < size-1; i++) {int val = qu2.poll();qu1.offer(val);}return qu2.poll();}}//peekpublic int top() {if(empty()) {return -1;//两个队列都为空,意味着当前的栈为空}if(!qu1.isEmpty()) {int size = qu1.size();int val = -1;for (int i = 0; i < size; i++) {val = qu1.poll();qu2.offer(val);}return val;}else {int size = qu2.size();int val = -1;for (int i = 0; i < size; i++) {val = qu2.poll();qu1.offer(val);}return val;}}public boolean empty() {return qu1.isEmpty() && qu2.isEmpty();}
}
4.4 栈实现队列
栈实现队列,还是老样子,我们还是来看看队列的工作状态
具体我们使用俩个栈模拟出队的操作
具体代码:
import java.util.Stack;class MyQueue {private Stack<Integer> stack1;private Stack<Integer> stack2;public MyQueue() {stack1 = new Stack<>();stack2 = new Stack<>();}public void push(int x) {stack1.push(x);}public int pop() {if(empty()) {return -1;}if(stack2.empty()) {while (!stack1.empty()) {stack2.push(stack1.pop());}}return stack2.pop();}public int peek() {if(empty()) {return -1;}if(stack2.empty()) {while (!stack1.empty()) {stack2.push(stack1.pop());}}return stack2.peek();}public boolean empty() {return stack1.empty() && stack2.empty();}
}
五.总结
- 队列是一种先入先出的线性表数据结构
- 可以使用数组或链表实现队列,实现需要包含的方法有入队add、出队remove等
- 队列操作的时间复杂度均为O(1),不受队列大小影响