目录
1. 概念
2. 队列的使用
3. 自己动手实现队列
3.1 MyQueue接口
3.2 LinkedQueue类
3.3 入队列
3.4 出队列
3.5 获取队头元素
3.6 获取队列中有效元素个数与检测队列是否为空
3.7 toString方法
4. 整体实现
4.1 LinkedQueue类
4.2 Test类
4.3 测试结果
5. 循环队列
6. 实现循环队列
6.1 LoopQueue类
6.2 判断队列是否为满与是否为空&获取队列中有效元素的个数
6.3 入队列
6.4 出队列
6.5 获取队头元素
6.6 toString方法
7. 整体实现循环队列
7.1 LoopQueue类
7.2 Test类
7.3 测试结果
8. 双端队列 (Deque)
8.1 双端队列的线性实现
8.2 双端队列的链式实现
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)。
入队列:进行插入操作的一端称为队尾(Tail/Rear)
出队列:进行删除操作的一端称为队头(Head/Front)
在Java中,Queue是个接口,底层是通过链表实现的。
【注意】Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。
public static void main(String[] args) {
Queue 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());
}
}
队列中既然可以存储元素,那底层肯定要有能够保存元素的空间,通过前面线性表的学习了解到常见的空间类型有两种:顺序结构 和 链式结构。思考下:队列的实现使用顺序结构还是链式结构好?
public interface MyQueue {
void offer( E val);
E pop();
E peek();
boolean isEmpy();
int size();
}
public class LinkedQueue implements MyQueue {
private Node head;//头节点
private Node tail;//尾节点
private int size; // 车厢节点个数,保存的元素个数
//车厢类的定义,车厢作为火车的内部类,对外部完全隐藏
private class Node {
E val;//保存的元素
Node next;//下节车厢的位置
Node(E val) {
this.val = val;
}
}
}
public void offer(E val) {
Node node = new Node(val);
if (size == 0){
head = node;
tail = node;
}
tail.next = node;
tail = node;
size ++;
}
public E pop() {
if(size == 0){
throw new IllegalArgumentException("Queue is empty,cannot pop!");
}
Node node = head;
head = head.next;
size --;
return node.val;
}
public E peek() {
if(size == 0){
throw new IllegalArgumentException("jjj");
}
return head.val;
}
public boolean isEmpy() {
return size == 0;
}
public int size() {
return size;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("head [");
for (Node x = head; x != null; x = x.next) {
sb.append(x.val);
if(x.next != null){
sb.append("->");
}
}
sb.append("] tail");
return sb.toString();
}
import MyQueue;
public class LinkedQueue implements MyQueue {
private Node head;//头节点
private Node tail;
private int size; // 车厢节点个数,保存的元素个数
//车厢类的定义,车厢作为火车的内部类,对外部完全隐藏
private class Node {
E val;//保存的元素
Node next;//下节车厢的位置
Node(E val) {
this.val = val;
}
}
@Override
public void offer(E val) {
Node node = new Node(val);
if (size == 0){
head = node;
tail = node;
}
tail.next = node;
tail = node;
size ++;
}
@Override
public E pop() {
if(size == 0){
throw new IllegalArgumentException("jjj");
}
Node node = head;
head = head.next;
size --;
return node.val;
}
@Override
public E peek() {
if(size == 0){
throw new IllegalArgumentException("jjj");
}
return head.val;
}
@Override
public boolean isEmpy() {
return size == 0;
}
@Override
public int size() {
return size;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("head [");
for (Node x = head; x != null; x = x.next) {
sb.append(x.val);
if(x.next != null){
sb.append("->");
}
}
sb.append("] tail");
return sb.toString();
}
}
public class LinkedQueueTest {
public static void main(String[] args) {
LinkedQueue queue = new LinkedQueue<>();
queue.offer(1);
queue.offer(2);
queue.offer(3);
queue.offer(4);
queue.offer(5);
System.out.println(queue);
System.out.println("出队列");
queue.pop();
System.out.println(queue);
System.out.println("查看队头元素");
System.out.println(queue.peek());
System.out.println("获取队长以及判断队列是否为空");
System.out.println(queue.size() + " " + queue.isEmpy());
}
}
实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。循环队列通常使用数组实现。
【数组下标循环的小技巧】
在循环队列的数组中,下标进行取模操作 :
如何区分空与满
1. 通过添加 size 属性记录
2. 保留一个位置
3. 使用标记
public class LoopQueue implements MyQueue {
private int head;
private int tail;
private int size;
// size = (head - tail + data.length) % data.length;
Object[] data;
public LoopQueue() {
data = new Object[10];
}
public LoopQueue(int size) {
data = new Object[size + 1];
}
}
private boolean isFull(){
return (tail + 1) % data.length == head;
// return size == data.length -1;
}
@Override
public boolean isEmpy() {
// return size == 0;
return tail ==head;
}
@Override
public int size() {
return size;
}
public void offer(E val) {
if (isFull()){
throw new NoSuchElementException("LoopQueue is full , cannot offer!!!");
}
data[tail] = val;
tail = (tail + 1) % data.length;
size++;
}
public E pop() {
if (isEmpy()){
throw new IllegalArgumentException("LoopQueue is empty , cannot pop!!!");
}
E val = (E) data[head];
head = (head + 1) % data.length;
size --;
return val;
}
public E peek() {
if (isEmpy()){
throw new IllegalArgumentException("LoopQueue is empty , cannot peek!!!");
}
return (E) data[head];
}
public String toString() {
StringBuilder sb = new StringBuilder();
int j = head;
sb.append("fornt [");
for (int i = 0; i < size; i++) {
sb.append(data[j]);
if(i != size - 1){
sb.append(", ");
}
j = (j + 1) % data.length;
}
sb.append("] tail");
return sb.toString();
}
import queue.MyQueue;
import java.util.NoSuchElementException;
public class LoopQueue implements MyQueue {
private int head;
private int tail;
private int size;
Object[] data;
public LoopQueue() {
data = new Object[10];
}
public LoopQueue(int size) {
data = new Object[size + 1];
}
@Override
public void offer(E val) {
if (isFull()){
throw new NoSuchElementException("LoopQueue is full , cannot offer!!!");
}
data[tail] = val;
tail = (tail + 1) % data.length;
size++;
}
@Override
public E pop() {
if (isEmpy()){
throw new IllegalArgumentException("LoopQueue is empty , cannot pop!!!");
}
E val = (E) data[head];
head = (head + 1) % data.length;
size --;
return val;
}
@Override
public E peek() {
if (isEmpy()){
throw new IllegalArgumentException("LoopQueue is empty , cannot peek!!!");
}
return (E) data[head];
}
private boolean isFull(){
return (tail + 1) % data.length == head;
// return size == data.length -1;
}
@Override
public boolean isEmpy() {
// return size == 0;
return tail ==head;
}
@Override
public int size() {
return size;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
int j = head;
sb.append("fornt [");
for (int i = 0; i < size; i++) {
sb.append(data[j]);
if(i != size - 1){
sb.append(", ");
}
j = (j + 1) % data.length;
}
sb.append("] tail");
return sb.toString();
}
}
public class LoopQueueTest {
public static void main(String[] args) {
LoopQueue queue = new LoopQueue<>(5);
queue.offer(1);
queue.offer(2);
queue.offer(3);
queue.offer(4);
queue.offer(5);
System.out.println(queue);
System.out.println("出队列");
queue.pop();
System.out.println(queue);
System.out.println("查看队头元素");
System.out.println(queue.peek());
System.out.println("两次出队列");
queue.pop();
queue.pop();
System.out.println("查看队头元素");
System.out.println(queue.peek());
System.out.println(queue);
System.out.println("获取队长以及判断队列是否为空");
System.out.println(queue.size() + " " + queue.isEmpy());
}
}
双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。 那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。
【注意】Deque是一个接口,使用时必须创建LinkedList的对象。
在实际工程中,使用Deque接口是比较多的,栈和队列均可以使用该接口。
Deque stack = new ArrayDeque<>();//双端队列的线性实现
Deque queue = new LinkedList<>();//双端队列的链式实现
public static void main(String[] args) {
// 可以使用双端队列作为栈使用,API
Deque stack = new ArrayDeque<>();
stack.push(1);
stack.push(3);
stack.push(5);
// 5
System.out.println(stack.pop());
// 3
System.out.println(stack.peek());
}
public static void main(String[] args) {
// 可以使用双端队列作为栈使用,API
Deque stack = new LinkedList<>();
stack.offer(1);
stack.offer(3);
stack.offer(5);
// 1
System.out.println(stack.poll());
// 3
System.out.println(stack.peek());
}