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