(一)什么是队列?
队列又叫先进先出表,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和另一端进行取数据操作,插入数据的一端是队尾,取数据的一端是队头
(二)队列取、添加数据操作的抽象形式
为了形象的表达队列,使用了管道进行表示(因为队列只能在队头和队尾进行操作,而它刚好有两端的出口)(在实际队列中是不存在的),在管道中存储的数据是有序的,它按照添加数据的先后来进行排序,并且管道中的操作只能在两端进行,因此说明队列是无法进行中间插入操作,它只能在队尾中进行添加
(三)队列的结构
1、顺序表表示(空队列:head == tail )
2、单链表表示(head = node tail = node.next)
(四)队列的方法
add(Object o):
添加一个元素,返回boolean类型的值,如果队列已满,则抛出一个IllegalStateException异常,如果添加的元素为nulll,则抛出NullPointerException异常
offer(Object o):
添加一个元素,如果添加成功则返回true,否则返回false,如果添加的元素为nulll,则抛出NullPointerException异常
remove():
移除元素(队头),返回的是一个Object类型的数据(移除的数据),如果队列为空,则抛出NoSuchElementException异常
poll():
移除元素(队头),返回的是一个Objcet类型的数据(移除的数据),如果队列为空,则返回null
element():
返回队头的元素,但是没有移除它,如果队列为空,则抛出NoSuchElementException异常
peek():
返回队头的元素,但是没有移除它,如果队列为空,则返回null
(五)使用顺序表实现队列
/**
* 队列:
* 增加:在队尾进行
*查询:根据下标查询
* 删除:在对头中进行,不能对对头以外的
* 队列中没有改操作
*/
public class myQueue<T> {
//使用顺序表
private Object[] elements;
private int size;
private int head;
private int tail;
public myQueue(int space){
elements = new Object[space+1];
size = 0;
head = 0;
tail = 0;
}
//添加元素
public boolean add(T data){
checkPositionTail(tail);
//队尾不能小于队头
checkQueue();
//队列已满,抛出IllegalStateException异常
if (tail == elements.length-1) throw new IllegalStateException("队列已满"+tail);
//如果添加的元素为null,则抛出NullPointerException异常
if (data == null) throw new NullPointerException("队列中不允许出现null元素");
//向数组中添加元素
elements[tail] = data;
tail++;
size++;
return true;
}
public boolean offer(T data){
//队尾不能小于队头
checkQueue();
//如果添加的元素为null,则抛出NullPointerException异常
if (data == null) throw new NullPointerException("队列中不允许出现null元素");
//如果下标不符合则返回false(添加不成功),添加成功返回true
if (tail < 0 || tail >= elements.length-1){
return false;
}else{
elements[tail] = data;
tail++;
size++;
return true;
}
}
//判断是否溢出
private void checkPositionTail(int tail) {
if (tail < 0 || tail>= elements.length) throw new IndexOutOfBoundsException("溢出");
}
//队头和队尾的判断 (这句有没有都没关系,因为如果队列为空时抛出异常结束了任务)
private void checkQueue(){
//队尾不能小于队头
if (tail < head){
throw new IndexOutOfBoundsException("不符合队列规则");
}
}
//移除元素
public T remove(){
checkQueue();
checkPositionTail(tail);
//如果队列为空,则抛出NoSuchElementException异常
if (head == tail) throw new NoSuchElementException("队列为空");
T data = (T) elements[head]; //得到队头的数据
elements[head] = null;
head++; //改变队头
size--;
return data;
}
public T poll(){
checkQueue();
checkPositionTail(tail);
//如果队列为空,则返回null
if (head == tail){
return null;
}else{
T data = (T) elements[head]; //得到队头的数据
elements[head] = null;
head++; //改变队头
size--;
return data;
}
}
//查询
public T element(){
checkQueue();
checkPositionTail(tail);
//如果队列为空,则抛出NoSuchElementException异常
if (tail == head) throw new NoSuchElementException("队列为空");
return (T) elements[head];
}
public T peek(){
checkQueue();
checkPositionTail(tail);
if (tail == head) return null;
else
return (T) elements[head];
}
public static void main(String[] args) {
myQueue q = new myQueue(5);
for (int i = 0; i < 5; i++) {
q.offer(i);
//q.add(i);
}
//q.add(6);
//System.out.println(q.offer(6));
// q.remove();
q.poll();
System.out.println("队头元素"+q.peek());
int size = q.size; //不能直接使用q.size放到for循环中判断,因为每次判断时都调用q.size,而q.size是动态变化的(poll()中size--)
for (int i = 0; i < size; i++) {
q.poll();
}
System.out.println("队头元素"+q.element());
}
}
运行结果截图:
(六)使用链式表实现队列
import java.util.NoSuchElementException;
//使用单链表实现队列
public class myLinkedQueue<T> {
private int size;
private Node headNode;
private Node head;
private Node tail;
public myLinkedQueue(){
int size = 0;
}
//添加数据
public boolean offer(T data){
//如果队列为空,则抛出NoSuchElementException异常
if (data == null) throw new NoSuchElementException("添加的元素不能为null");
Node newNode = new Node(); //创建新的节点
newNode.data = data;
newNode.next = tail;
if (size == 0){
head = newNode;
}else{
headNode.next = newNode;
}
headNode = newNode; //记录添加的新节点
size++;
return true;
}
//移除元素
public T poll(){
if (head == tail){
return null;
}else{
Node target = head;
head = head.next;
T data = target.data;
target.next = null;
target.data = null; //gc
size--;
return data;
}
}
//获取头元素
public T element(){
//如果队列为空,则抛出NoSuchElementException异常
if (head == tail) throw new NoSuchElementException("队列为空");
return head.data;
}
//返回节点的数目
public int size(){
return size;
}
//节点类
class Node{
private Node next;
private T data;
}
public static void main(String[] args) {
myLinkedQueue q = new myLinkedQueue();
for (int i = 0; i < 5; i++) {
q.offer(i);
}
System.out.println(q.element());
while (q.head!=q.tail){
q.poll();
}
System.out.println(q.element());
}
}
运行结果截图:
问题小答:
Q:在使用顺序表实现队列中为什么初始化数组大小的时候要加1?
R:在使用队列中,我们传入的int参数表示我们希望在队列中存储的个数,而队列中队尾始终是指向一个空的数组空间,如果我们没有加1的话,我们储存数据的空间就等于传入的参数减1
Q:为什么队尾要始终指向null?
R:我们知道判断一个队列是否为空的语句是head ?= tail,如果队尾没有指向null的话,等到head ==tail理论上我们的队列为空了,但是实际上我们还存储一个元素,所以队尾要始终指向null
Q:在使用顺序表实现队列时,初始化的空间大小是5,开始时队列填满了,我们移除2个元素,理论上我们还可以继续添加2个元素,那么为什么抛出IndexOutOfBoundsException异常?
R:通过上面顺序表实现队列的代码我们可以知道,队列中我们移除元素的操作实际是将队头的指向往下移,队尾始终是不变的(在这个始终指向队列最后的空间),如果再添加元素时,那么队尾将指向下一个空间,可是这时它已经指向的是这个数组空间的最大值了,如果再下移的话那么就会抛出IndexOutOfBoundsException异常,这种现象称为"假溢出"
Q:我们有什么方法解决这种"假溢出"的现象吗?
R:我们可以使用循环队列,什么是循环队列?请看下一次分析
Q:在上面使用单链表实现的队列中会出现"假溢出"这种现象吗?
R:不会,因为"假溢出"出现的原因是存储空间的不足的假现,而在链表中存储空间是动态,它根据队列的元素变化而变化
以上是对单向队列的分析及实现,如果对你有帮助的话点个赞👍哦,谢谢(*^_^*)