队列也是一种特殊的线性表,它只能从表尾插入数据元素,在表头删除数据元素,所以队列的操纵和栈一样操作受限,队列具有先进先出(FIFO(First In First Out))的特性,就如同在生活中的排队一般。
允许进行删除的一端称为队首(front),允许插入的一端称为队尾(rear)
初始化队列时,令front = rear = 0;入队时,直接将新的数据元素存入rear所指向的存储单元中,然后将rear的值增加1;出队时,直接取出front所指向的存储单元中的数据元素得值,然后front++。
因为队列的机制为队首出队队尾入队,所以当我们多次进行入队出队操作之后,队首指针front和队尾指针rear会后移,当rear移动到顺序表最大长度时,便不能进行入队操作,但是队首指针所指结点之前的位置为空,但是计算机任然会认为此表满,这种情况我们称为假溢出,防止假溢出的方法为:将队列做成循环队列,这样rear指针在队尾跳转时就不会显示队满,而会跳转至队首。
当我们用循环队列解决假溢出问题时,我们在对队列进行入队出队操作时又会发现新的问题,那就是队空队满同条件,通常我们采用以下三种方法:
当顺序存储空间的最大容量为maxSize
时,我们允许存放的最大的元素个数为maxSize-1
此时,队空的判断条件为:front == rear
而队满的判断条件为:front == (rear + 1) % maxSize
这里采用的是一种取余运算,此种算法在后期运用特别多
本种方法下标不固定,front指针与rear指针位置会随着入队出队的变化而变化
//判断是否为空
public boolean isEmpty(){
return front == rear; //少用一个存储单元
}
//获取长度
public int length(){
return (rear - front + queueElem.length) % queueElem.length;
}
在设计程序的时候引进一个变量flag,其初始值为0,每当有入队操作时,就将其置为flag == 1
,每当有出队操作时, 就将其置为flag == 0
;队空的判断条件为front == rear && flag == 0
;此时队满的判断条件为front == rear && flag == 1
//判断是否为空
public boolean isEmpty(){
return front == rear && flag == 0; //设置一个标志变量
}
//获取长度
public int length(){
return (rear - front + queueElem.length) % queueElem.length;
}
在设计程序的时候引入一个变量num;设置其初始值为0;每当有入队操作时,就将其num值加一,出队操作成功之后就将num值减一,此时队空的判断条件为num == 0
而队满时的判断条件为num > 0 && front == rear
//判断是否为空
public boolean isEmpty(){
return num == 0; //使用计数器
}
//获取长度
public int length(){
return (rear - front + queueElem.length) % queueElem.length;
}
当前代码提供三种方法得入队操作,分别对应上述解决队空队满同条件得三种方法
//入队
public void offer(Object x) throws Exception{
// if((rear + 1) % queueElem.length == front) //少用一个存储单元
if(front == rear && flag == 1) //设置一个标志变量flag
// if(num > 0 && front == rear) //计数器
throw new Exception("队列已满");
else{
queueElem[rear] = x;
rear = (rear + 1) % queueElem.length;
flag = 1; //标志量flag
// num++; //计数器
}
}
当前代码提供三种方法得出队操作,分别对应上述解决队空队满同条件得三种方法
//出队
public Object poll(){
if(isEmpty()){
return null;
}else{
Object t = queueElem[front];
front = (front + 1) % queueElem.length; //front值循环+1
flag = 0; //标志量flag
// num--; //计数器
return t;
}
}
方法类
public class CircleSqQueue {
Object[] queueElem;
int front;
int rear;
int flag = 0; //设置标志位flag
int num = 0; //设置计数器
//构造函数
public CircleSqQueue(int maxSize){
front = rear = 0;
queueElem = new Object[maxSize];
}
public CircleSqQueue(int maxSize, Object[] temp){
this(maxSize);
for (int i = 0; i < temp.length; i++) {
queueElem[rear] = temp[i];
rear = (rear + 1) % queueElem.length;
flag = 1; //设置标志位flag
// num++; //计数器
}
}
//置空
public void clear(){
front = rear = 0;
flag = 0; //标志量flag
// num = 0; //计数器
}
//判断是否为空
public boolean isEmpty(){
// return front == rear; //少用一个存储单元
return front == rear && flag == 0; //设置一个标志变量
// return num == 0; //使用计数器
}
//获取长度
public int length(){
return (rear - front + queueElem.length) % queueElem.length;
}
//取队列首元素
public Object peek(){
if(isEmpty())
return null;
else
return queueElem[front];
}
//入队
public void offer(Object x) throws Exception{
// if((rear + 1) % queueElem.length == front) //少用一个存储单元
if(front == rear && flag == 1) //设置一个标志变量flag
// if(num > 0 && front == rear) //计数器
throw new Exception("队列已满");
else{
queueElem[rear] = x;
rear = (rear + 1) % queueElem.length;
flag = 1; //标志量flag
// num++; //计数器
}
}
//出队
public Object poll(){
if(isEmpty()){
return null;
}else{
Object t = queueElem[front];
front = (front + 1) % queueElem.length; //front值循环+1
flag = 0; //标志量flag
// num--; //计数器
return t;
}
}
//打印输出整个队列
public void display(){
if(!isEmpty()){
// for (int i = front; i != rear; i = (i + 1) % queueElem.length) //少用一个存储单元
// System.out.print(queueElem[i].toString() + " ");
if(front == rear && flag == 1){
for (int i = front, j = queueElem.length; j > 0; i = (i + 1) % queueElem.length,j--) //设置标志量flag
System.out.print(queueElem[i].toString() + " ");
}else{
for (int i = front; i != rear; i = (i + 1) % queueElem.length) //设置标志量flag
System.out.print(queueElem[i].toString() + " ");
}
// if(front == rear && num > 0){
// for(int i = front, j = num; j > 0; i = (i + 1) % queueElem.length, j--) //计数器
// System.out.print(queueElem[i].toString() + " ");
// }else{
// for(int i = front; i != rear && num != 0; i = (i + 1) % queueElem.length) //计数器
// System.out.print(queueElem[i].toString() + " ");
// }
}else{
System.out.println("此队列为空");
}
}
}
队列的链式存储结构可以不用带头结点的单链表来实现。为了便于实现入队和出队操作,需要引进两个指针front和rear来分别指向队首元素和队尾元素的结点。
在链式存储结构中我们也需要使用到前面链表中的结点类Node类
。
对于链队列的入队操作,我们需要先构造一个新的数据域为x的结点,然后判断整个队列是否为空,因为如果为空,则直接让队首指针和队尾指针指向新结点即可,否则就需要找到队尾指针指向结点的指针域,然后将新结点的地址交给它,再让队尾指针后移,否则会报空指针异常NullPointerException
//入队
public void offer(Object x){
Node p = new Node(x); //初始化新结点
if(!isEmpty()){
//判空
rear.next = p; //连接新结点
rear = rear.next; //尾指针后移
// rear = p;
}else
front = rear = p; //首结点
}
对于出队我们也需要对其进行判空,如果队列为空则直接返回null
,没有其他操作,否则就需要先保存队首结点,然后队首指针后移,最后返回删除的旧队首结点的数据域。
//出队
public Object poll(){
if(!isEmpty()){
//判空
Node p = front; //新指针p指向队首指针指向的结点
front = front.next; //首结点后移
if(p == rear) //若p指向尾指针
rear = null; //则将尾指针指向结点(最后一个结点)出队
return p.data; //返回p指向结点的数据域
}else
return null; //返回空值
}
方法类
public class LinkQueue {
private Node front; //队首指针
private Node rear; //队尾指针
//链队列的构造函数
public LinkQueue(){
front = rear = null; //将队首指针和队尾指针都置空
}
//链队列构造完整链表
public LinkQueue(Object[] temp){
this();
for (int i = 0; i < temp.length; i++) {
offer(temp[i]);
}
}
//队列置空
public void clear(){
front = rear = null; //将队首指针和队尾指针都置空
}
//队列判空
public boolean isEmpty(){
return front == null; //队首指针为空则空
}
//求队列长度
public int length(){
Node p = front; //将p指向队首指针指向的位置
int length = 0; //设置长度默认值为0
while(p != null){
//若p指向位置不为空
p = p.next; //指针下移
++length;; //计数器+1
}
return length; //返回长度值
}
//取队首元素
public Object peek(){
if(!isEmpty()) //判空
return front.data; //返回首元素数据域、
else
return null; //返回空
}
//入队
public void offer(Object x){
Node p = new Node(x); //初始化新结点
if(!isEmpty()){
//判空
rear.next = p; //连接新结点
rear = rear.next; //尾指针后移
// rear = p;
}else
front = rear = p; //首结点
}
//出队
public Object poll(){
if(!isEmpty()){
//判空
Node p = front; //新指针p指向队首指针指向的结点
front = front.next; //首结点后移
if(p == rear) //若p指向尾指针
rear = null; //则将尾指针指向结点(最后一个结点)出队
return p.data; //返回p指向结点的数据域
}else
return null; //返回空值
}
//输出队列
public void display() throws Exception {
if(!isEmpty()){
Node p = front; //新指针指向队首指针指向的结点
while(p != null){
System.out.print(p.data + " ");
p = p.next;
}
}else{
throw new Exception("此队列为空");
}
}
}
Node类
结点类与之前发表博客中的链表中结点类一致
- 都是线性结构,即数据元素之间具有“一对一”的逻辑关系。
- 插入操作都是限制在表尾进行
- 都可以在顺序存储结构和链式存储结构上实现
- 在时间代价上,插入与删除操作都需要常数时间
(O(1))
;在空间代价上,情况也相同- 多链栈和多链队列的管理模式可以相同
- 删除数据元素操作的位置不同。栈的删除操作控制在表尾进行,而队列的删除操作控制在表尾进行
- 两者的应用场合不同(FILO与FIFO)
- 顺序栈可以实现多栈空间共享,而顺序队列不行