队列的定义
**队列是只允许在一端进行插入操作、另一端进行删除操作的线性表。**队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的端称为队尾,允许删除的一端称为队头。
队列的结构
线性表有顺序存储和链式存储,栈是线性表,所以有这两种存储方式。同样,队列作为一种特殊的线性表,也同样存在这两种存储方式。
循环队列
队列顺序存储的不足
在队尾追加元素,不需要移动任何元素,时间复杂度为O(1)
队列元素的出列是在队头,即下标为0的位置,那也就意味着,队列中的所有元素都得向前移动,以保证队列的队头,也就是下标为0的位置不为空,此时时间复杂度为0(n)
循环队列
把队列头尾相接的顺序存储结构称之为循环队列。
队列满的条件
(rear + 1) %QueueSize = front
左图,当队列空时,条件就是front = rear, 当队列满时,我们修改其条件,保留一个元素空间。
右图。空队列时,front 等于tear, 现在当队列满时,也是front等于rear。
为了区分空队列和满队列,我们采用左图的方案。
队列长度的计算公式
(rear - front + QueueSize)%QueueSize
结构
static class SqQueue {
int rear;
int front;
int[] data;
}
入队
若队列未满,则插入元素elementData为queue新的队尾元素
static void EnQueue(SqQueue queue, Integer elementData) throws Exception {
if((queue.rear + 1) % MAXSIZE == queue.front) {
throw new Exception("队列已满");
}
queue.data[queue.rear] = elementData; // 将元素赋值给队尾
queue.rear = (queue.rear + 1) % MAXSIZE; // rear后移一位置,若到最后则转到数组头部
}
出队
若队列不空,则删除queue中队头元素,返回其值
static Integer DeQueue(SqQueue queue) throws Exception {
if(queue.rear == queue.front) {
throw new Exception("队列是空的");
}
int result = queue.data[queue.front]; // 将对头元素赋值result
queue.front = (queue.front + 1) % MAXSIZE; // front后移一位置,若到最后则转到数组头部
return result;
}
不足
单是顺序存储,若不是循环队列,算法的时间性能是不高的,但循环队列又面临着数组可能会溢出的问题,所以我们还需要研究一下不需要担心队列长度的链式存储结构。
链队列
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。将队头指针指向链队列的头结点,而队尾指针指向终端结点。空队列时,front和rear都指向头结点。
结构
static class Node{
int data;
Node next;
public Node() { }
}
static class LinkQueue {
Node front;
Node rear;
public LinkQueue() { }
}
入队
入队操作,就是在链表尾部插入结点
static void EnQueue(LinkQueue queue, int elementData) {
Node newNode = new Node();
newNode.data = elementData;
queue.rear.next = newNode; // 把新结点node赋值给原队尾结点的后继,上图①
queue.rear = newNode; // 将新结点设置为队尾结点。上图②
}
出队
出队操作时,就是头结点的后继结点出队,将头结点的后继改为它后面的结点,若链表除头结点外只剩一个元素时,则需将rear指向头结点。
static Integer DeQueue(LinkQueue queue) throws Exception {
if(queue.rear == queue.front) {
throw new Exception("队列是空的");
}
Node p = queue.front.next; // 将预删除的结点赋值给p 1
int result = p.data;
queue.front.next = p.next; // 将原对头结点的后继p.next赋值给头结点后继 2
if(queue.rear == p) { // 若队头是队尾,则删除后将rear指向头结点 3
queue.rear = queue.front;
}
return result;
}
总结
循环队列与链队列的比较
时间上,其实它们的基本操作都是常数时间,即都为0(1]的, 不过循环队列是事先申请好空间,使用期间不释放,而对于链队列,每次申请和释放结点也会存在一些时间开销, 如果入队出队频繁,则两者还是有细微差异。
空间上,循环队列必须有一个固定的长度,所以就有了存储元素个数和空间浪费的问题。而链队列不存在这个问题,尽管它需要一个指针域,会产生一些空间 上的开销,但也可以接受。所以在空间上,链队列更加灵活。
在可以确定队列长度最大值的情况下,建议用循环队列,如果你无法预估队列的长度时,则用链队列。