一、队列的定义及特点
队列是一种只允许在表的一端删除(称作表头)和表的另一端加入(称作表尾)的线性表。
从定义可以看出队列的特点是先进先出。
二、顺序表示的循环队列
概括来说循环队列是通过取模操作解决队列空间假溢出的问题,能更充分的利用所分配空间。因为在一般队列的顺序表示中,无论增加还是删除,头指针或尾指针都会递加,这样就会导致数组的低索引位置不可再被使用。
这种弊端在链式表示中不存在,因为链式表示中并不是一次开辟所有空间,而是动态开辟,并且删除的结点空间可以被释放。
这里只实现顺序表示的循环队列,个人认为链表的循环没有太大意义,如果只是循环而不增加队列长度,那么顺序表示更方便,如果需要增加队列长度的话,附带头尾指针的链表同样可以替代。
详细过程见代码
public class CircularQueue {
private int[] arr;
private int mod;
private int head; // 头指针,指向队列第一个元素
private int tail; // 尾指针,指向队列最后一个元素的下一个位置
private int size; // 队列中已有的元素数目
public CircularQueue(int size) {
mod = size + 1; // 这里的模取mod=size+1是因为下面选用的队列判满条件会导致少用一个元素空间
arr = new int[mod];
head = tail = this.size = 0; // 初始化头尾指针 都指向0
}
private boolean isEmpty() { // 判断队空
return head == tail;
}
private boolean isFull() {
// 判断队满,这种取模操作必定会导致减少一个可使用空间,并且注意这个不可用空间并不固定。
return (tail + 1) % mod == head;
}
public void inQueue(int num) {
if (isFull()) { // 入队前判断队满
System.out.println("队满!");
} else {
arr[tail] = num;
tail = (tail + 1) % mod;
size++;
}
}
public int outQueue() {
if (isEmpty()) { // 出队前判断队空
System.out.println("队空!");
return -1;
} else {
int num = arr[head];
head = (head + 1) % mod;
size--;
return num;
}
}
public int getSize() {
// 直接tail-head可能会出现负数,这是因为循环过程中两指针不断移动,会出现tail<head的情况。
return (tail - head + mod) % mod;
}
}
重点要理解为什么mod=size+1。因为如果以实际的队列长度size为模,会出现队列判空和判满条件一致的情况,即:
- 队列为空时,有tail=head
- 队列为满时,同样有tail=head
此时头尾指针相遇对应两种情况,为了避免这种情况,在实际队列长度的基础上+1,多空出一个元素空间,此时:
- 当队列为空,仅有tail=head,头尾指针相遇的这种情况
- 队列满时,有(tail+1)%mod=head
三、链式表示的一般队列
public class LinkedQueue {
private class Node { // 使用单链表存储
int data;
Node next;
public Node() {
}
public Node(int num) {
data = num;
}
}
Node head; // 头结点,指向队列中的第一个结点
Node tail;
int size;
public LinkedQueue() {
head = new Node(); // 初始化,新建头结点,尾指针指向头结点
tail = head;
size = 0;
}
public void inQueue(int num) { // 入队不需要判断队满,入队元素连接在队尾,尾指针后移
Node node = new Node(num);
tail.next = node;
tail = node;
size++;
}
public int outQueue() { // 出队需要判断队空
if (head == tail) {
System.out.println("队空!");
return -1;
}
if (head.next == tail) { // 队列中只有一个结点时断开头结点的引用
Node node = tail;
head.next = null;
tail = head;
size--;
return node.data;
} else { // 否则头结点指针指向这个结点的下一个结点
Node node = head.next;
head.next = head.next.next;
size--;
return node.data;
}
}
public int getSize() {
return size;
}
}
队列判空条件同样可以使用成员变量size。链队的删除还应该注意要释放结点空间,但因为java中的GC在断开结点所有引用之后会自动回收,所以省略了这一步。
补充:用两个栈实现队列
很简单,具体代码就不写了,思路是一个栈用作入队,一个栈用作出队,当出队栈空时将入队栈中的所有元素依次弹出压入到出队栈中。