队列是一种操作受限的线性表,和栈类似,队列的限制主要是运行在表的一端进行插入操作,而在表的另一端进行删除的操作(栈的操作受限是只能在一端进行插入和删除)。队列中进行插入的一端称为队尾 ,删除的一端称为队首。
队列的操作规则是先进先出,先入队的元素先出队,所以队列又被称为先进先出表。队列分为线性队,链队,循环队列和双端队列。
顺序队,采用顺序存储方式的队列称为顺序队,就是在内存中分配一段连续的存储空间来存放元素,常用的方式就是采用数组来实现。
顺序队列的实现和出入队的方法可以是:先定义一个数组用来存储数据,定义一个front和一个rear变量来辅助元素的出入队。当元素入队时,rear指针(变量)移动,然后将元素放在数组rear位置下(数组名[rear]),当rear=maxsize(数组大小)-1时,队列满了。元素出队时,front指针移动,然后将数组front位置下的元素删掉,当front=rear时,队列为空。
package Structures;
import java.util.Scanner;
public class ArrayQueue {
public static void main(String[] args) {
Queue queue = new Queue(5);
String key;
boolean loop = true;
Scanner scanner = new Scanner(System.in);
while (loop){
System.out.println("show: 输出队列");
System.out.println("exit: 退出");
System.out.println("add: 入队");
System.out.println("get: 出队");
System.out.println("请选择相关操作");
while (loop){
key = scanner.next();
switch (key){
case "show":
queue.list();
break;
case "exit":
scanner.close();
loop = false;
break;
case "add":
System.out.println("请输入值:");
int i = scanner.nextInt();
queue.addQueue(i);
break;
case "get":
try{
int pop = queue.getQueue();
System.out.print(pop);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
}
}
}
}
}
class Queue {
private int[] data;
private int front;
private int rear;
private int maxSize;
public Queue(int maxSize) {
this.maxSize = maxSize;
data = new int[maxSize];
front = -1;
rear = -1;
}
//判断队列是否为空
public boolean isEmpty() {
return rear == front;
}
//判断队列是否满
public boolean isFull(){
return rear == maxSize-1;
}
//入队
public void addQueue(int n){
if(isFull()){
System.out.println("队列已满");
return;
}
rear++;
data[rear] = n;
}
/*出队(注意:这里并不是真正的出队,只是将front移动返回值而已,实际上值还存在数组中,虽然数组是一个对象,但JavaApi并没有相对应的增删改查方法,可以借助ArrayUtils工具来将数组转为集合来操作)*/
public int getQueue(){
if(isEmpty()){
throw new RuntimeException("队列已经空了");
}
front++;
return data[front];
}
//遍历队列
public void list(){
if (isEmpty()){
System.out.println("队列为空");
return;
}
for (int i = 0; i < data.length; i++) {
System.out.print(data[i]);
}
}
}
在顺序队列中,由于是使用front和rear指针(变量)来辅助队列的进队和出队,在判断队列满时使用rear=maxsize(数组大小)-1,但这样子会出现**假溢出(尽管元素出队了,但随着front指针的移动,数组产生一些空位置)**的情况,导致数组不可复用的情况出现,而环形队列则可以解决这个问题。
环形队列主要是在顺序队的基础上,将数组的前端和后端连起来,形成一个环形数组,然后遵从队列的先进先出原则,被称为环形队列。
在创建循环队列时,和顺序队列不同的是,在循环队列中,front和rear的初始值都为0,而判断队满和队空的条件也不同,元素入队和出队的指针变量移动也不同(取余)。
队列的创建: 在循环队列中,front和rear的指针变量都等于0,直接指向数组的第一个元素,在循环队列中,数组存储元素的个数大小实际上比数组最大值少一个,因为要留一个空位来作为标记,比如上图的front和rear同时指向的那一个位置就是用来作为标记的。
public XQueue(int maxSize){
this.maxSize = maxSize;
front = 0;
rear = 0;
arr = new int[maxSize];
}
判断队空队满: 如上图所示:当rear+1= =front时,这时候表示队满了,因为循环队列要留一个位置来作为标志位,而在循环队列中,判断队满的条件是(rear+1)%maxSize == front ,因为是循环队列,rear的值是可以一直增长的,但(rear+1)% maxSize的范围一直在maxSize之内,比如说maxSize为4,那么(rear+1)%maxSize*的值就是0~4,范围在数组大小限制之内。而循环队列的判空的条件则是rear==front。
//队列判空
public boolean isFull(){
return (rear+1)%maxSize == front;
}
//队列是否满
public boolean isEmpty(){
return rear == front;
}
入队: 循环队列的入队分为两部分,首先是赋值然后是rear移位,之所以和顺序队不同是因为rear赋值时循环队列已经指向了数组的第一个位置0了,而rear的移位也和顺序队的不同,顺序队中rear的移位主要是rear = (rear+1)%maxSize,这样子rear的值尽管会一直增加,但+1并%maxSize后,rear最后的值都会在数组最大长度范围之内,不会造成数组下标越界的问题。
//进队
public void add(int n) {
if (isFull()) {
System.out.println("队列满了");
return;
}
arr[rear] = n;
//rear后移((rear+1)这里是会一直加的)
rear = (rear + 1) % maxSize;
}
出队: 在循环队列中元素的出队上,首先是获取到队首的元素,然后front指针移位,这里的front指针移位不是front++的了,而是front = (front+1)%maxSize,front最终的取值范围就不会超过数组长度,这样子的话front指针就会重新指回数组里的位置,从而形成环形队列。
//出队
public int get(){
if (isEmpty()){
throw new RuntimeException("队列为空");
}
int i = arr[front];
front = (front+1)%maxSize;
return i;
}
遍历: 循环队列中遍历数组的话,可以从队列中有效元素开始遍历,也就是从front开始,然后结束条件则是front加上队列中有效元素的个数,循环队列中计算队列中有效元素个数的是(rear+maxSize-front)%maxSize 。
//遍历
public void list(){
if (isEmpty()){
System.out.println("队空");
}
for (int i = front; i <front+(rear+maxSize-front)%maxSize; i++) {
System.out.println(arr[i%maxSize]);
}
}
package Structures;
import java.util.Scanner;
public class XHQueue {
public static void main(String[] args) {
XQueue queue = new XQueue(5);
String key;
boolean loop = true;
Scanner scanner = new Scanner(System.in);
while (loop){
System.out.println("show: 输出队列");
System.out.println("exit: 退出");
System.out.println("add: 入队");
System.out.println("get: 出队");
System.out.println("请选择相关操作");
while (loop){
key = scanner.next();
switch (key){
case "show":
queue.list();
break;
case "exit":
scanner.close();
loop = false;
break;
case "add":
System.out.println("请输入值:");
int i = scanner.nextInt();
queue.add(i);
break;
case "get":
try{
int pop = queue.get();
System.out.print(pop);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
}
}
}
}
}
class XQueue {
private int maxSize;
private int front;
private int rear;
private int[] arr;
public XQueue(int maxSize) {
this.maxSize = maxSize;
front = 0;
rear = 0;
arr = new int[maxSize];
}
//队列判空
public boolean isFull() {
return (rear + 1) % maxSize == front;
}
//队列是否满
public boolean isEmpty() {
return rear == front;
}
//进队
public void add(int n) {
if (isFull()) {
System.out.println("队列满了");
return;
}
arr[rear] = n;
//rear后移((rear+1)这里是会一直加的)
rear = (rear + 1) % maxSize;
}
//出队
public int get(){
if (isEmpty()){
throw new RuntimeException("队列为空");
}
int i = arr[front];
front = (front+1)%maxSize;
return i;
}
//遍历
public void list(){
if (isEmpty()){
System.out.println("队空");
}
for (int i = front; i <front+((rear+maxSize-front)%maxSize); i++) {
System.out.println(arr[i%maxSize]);
}
}
}
链队主要是使用链表来实现队列,在单链表中,我们可以指定结点的插入和删除方式来实现队列的先进先出规则,即使用尾插法插入结点(入队)和在头结点处删除结点(出队),从而实现链队。
如上所示,在实现链队的过程中,首先创建一个带有front和rear的头结点(也可以定义一个front首节点和一个rear尾结点,将链队理解为带有头结点和尾结点的单链表),当元素进队时,采用尾插法的方式插入结点,此时使用的辅助指针变量的是rear,因为rear主要是指向rear尾部的,当元素出队时,删除front指针变量指向的结点,就是front后面的那一个结点。
入队: 采用尾插法的方式插入数据结点,当队列为空时,将front和rear都指向新结点,当队列不为空时,直接使用尾结点rear来进行插入即可。
public void add(Node node) {
if (isEmpty()) {
rear = front = node;
length++;
} else {
rear.next = node;
rear = node;
length++;
}
}
出队: 链队中的出队分为两种情况,当只剩下一个元素时,出队后将front和rear置空,当队列中有超过两个元素时,只需将front指向下一个元素(就是队列中的第二个元素)即可。
public void pop() {
if (!isEmpty()) {
if (length == 1) {
front = rear = new Node("", "");
length--;
} else {
front = front.next;
length--;
}
}
}
package Structures;
import java.util.EmptyStackException;
public class LinkedQueue {
public static void main(String[] args) {
Node node01 = new Node("1", "1");
Node node02 = new Node("2", "2");
Node node03 = new Node("3", "3");
LQueue lQueue = new LQueue();
lQueue.add(node01);
lQueue.add(node02);
lQueue.add(node03);
lQueue.list();
lQueue.pop();
lQueue.list();
lQueue.pop();
lQueue.list();
}
}
//结点信息
class Node {
public String id;
public String name;
public Node next;
public Node(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
"}";
}
}
class LQueue {
//创建首节点和尾结点
Node front = new Node("", "");
Node rear = new Node("", "");
int length = 0;
public boolean isEmpty() {
return length == 0;
}
public void add(Node node) {
if (isEmpty()) {
rear = front = node;
length++;
} else {
rear.next = node;
rear = node;
length++;
}
}
public void pop() {
if (!isEmpty()) {
if (length == 1) {
front = rear = new Node("", "");
length--;
} else {
front = front.next;
length--;
}
}
}
//输出链表
public void list() {
Node temp = front;
while (true) {
if (temp == null) {
break;
}
System.out.print(temp);
temp = temp.next;
}
System.out.println();
}
}
双端队列是指两端都可以进行进队和出队操作的队列,和普通的队列不同,普通的队列只允许在队尾进队和队首出队,而双端队列则是允许可以在两端进行操作的队列。双端队列可以使用顺序队(循环队列)和链队来实现。
在双端队列中,进队时前端进的元素排列在队列中后端进的元素的前面,就是说在前端进的元素排在前面,但队列中元素的出队排序上,无论是前端出的还是后端出的,先出的都排在后出的前面。
双端队列可以看做是队列和栈的结合体,他既可以先进先出也可以后进先出。在双端队列中,可以将双端队列变成普通的队列也可以变成栈来使用,这些也被称为受限的双端队列。比如:前端进后端出就是队列,前端进前端出就是栈。可以根据自己的需求来确定双端队列最后的形式(先进先出还是后进先出)。
在双端队列的实现中,大部分都是使用循环队列来实现的,这样子可以解决数组假溢出的问题,同时循环队列中也有front和rear两个指针变量,对双端队列的操作可以比较容易实现。
其实双端队列使用循环队列来实现的话,可以理解为:front和rear同时指向了一个标志位,将这个标志位当成一个站在中间的人,前端进队的话就往左加,后端进队的话就往右加。
前端进队: 在前端入队上,主要是将front指针往前移动,从图中可以看出,front指针的移动从0指向了3,front的取值主要是(front-1+maxSize)%maxSize,这样子就可以取到相对应的数组下标值。
//前端入队
public void addFront(int n) {
if (isFull()) {
System.out.println("队列已满");
return;
}
//新元素插到原先元素的前面,先移动指针
front = (front - 1 + maxSize) % maxSize;
data[front] = n;
}
后端进队: 后端进队就和循环队列的进队方式类似了,将rear指针往前移动一位即可,即rear= (rear+1)%maxSize。注意要先赋值再移位,因为rear指针要指向的是该元素的下一个位置。
//后端进队
public void addRear(int n) {
if (isFull()) {
System.out.println("队列已满");
return;
}
//rear一开始指向的是一个空位置,先赋值
data[rear] = n;
rear = (rear + 1) % maxSize;
}
前端出队: 将front指针向前移动一位即可((front+1)%maxSize),注意的是,即使指针变量移动了一位,当实际上在数组中存储的数据还是存在的,并没有删除,可以通过后面添加进来的元素进行覆盖。
//前端出队
public void popFront() {
if (isEmpty()) {
System.out.println("队空");
return;
}
int datum = data[front];
front = (front + 1) % maxSize;
System.out.println("出队的元素为:" + datum);
}
后端出队: 将rear指针向后移动一位(rear-1+maxSize)%maxSize,之所以在括号里面加上maxSize,是因为rear-1有可能出现负数。
//后端出队
public void popRear() {
if (isEmpty()) {
System.out.println("队空");
return;
}
rear = (rear - 1 + maxSize) % maxSize;
int datum = data[rear];
System.out.println("出队的元素为:" + datum);
}
package Structures;
import java.util.Scanner;
public class DoubleQueue {
public static void main(String[] args) {
DQueue dQueue = new DQueue(5);
String key;
boolean loop = true;
Scanner scanner = new Scanner(System.in);
while (loop) {
System.out.println("show: 输出队列");
System.out.println("exit: 退出");
System.out.println("addFront: 前端入队");
System.out.println("getFront: 前端出队");
System.out.println("addRear: 后端入队");
System.out.println("getRear: 后端出队");
System.out.println("请选择相关操作");
while (loop) {
key = scanner.next();
switch (key) {
case "show":
dQueue.list();
break;
case "exit":
scanner.close();
loop = false;
break;
case "addFront":
System.out.println("请输入值:");
int i = scanner.nextInt();
dQueue.addFront(i);
break;
case "addRear":
System.out.println("请输入值:");
int a = scanner.nextInt();
dQueue.addRear(a);
break;
case "getFront":
System.out.println("请输入值:");
dQueue.popFront();
break;
case "getRear":
System.out.println("请输入值:");
dQueue.popRear();
break;
}
}
}
}
}
class DQueue {
private int maxSize;
private int front;
private int rear;
private int[] data;
public DQueue(int maxSize) {
this.maxSize = maxSize;
front = rear = 0;
data = new int[maxSize];
}
//判断队列是否为空
public boolean isEmpty() {
return front == rear;
}
//判断队列是否满了
public boolean isFull() {
return front == (rear + 1) % maxSize;
}
//前端入队
public void addFront(int n) {
if (isFull()) {
System.out.println("队列已满");
return;
}
//新元素插到原先元素的前面,先移动指针
front = (front - 1 + maxSize) % maxSize;
data[front] = n;
}
//后端进队
public void addRear(int n) {
if (isFull()) {
System.out.println("队列已满");
return;
}
//rear一开始指向的是一个空位置,先赋值
data[rear] = n;
rear = (rear + 1) % maxSize;
}
//前端出队
public void popFront() {
if (isEmpty()) {
System.out.println("队空");
return;
}
int datum = data[front];
front = (front + 1) % maxSize;
System.out.println("出队的元素为:" + datum);
}
//后端出队
public void popRear() {
if (isEmpty()) {
System.out.println("队空");
return;
}
rear = (rear - 1 + maxSize) % maxSize;
int datum = data[rear];
System.out.println("出队的元素为:" + datum);
}
//遍历队列
public void list() {
if (isEmpty()) {
System.out.println("队空");
}
for (int i = front; i < front + ((rear + maxSize - front) % maxSize); i++) {
System.out.println(data[i % maxSize]);
}
}
}