目录
Day 17:链表实现队列
Task:
- 链队列比较容易写.
- Node 类以前是 LinkdedList 的内嵌类, 这里重写了一遍. 访问控制的事情以后再说.
- 为方便操作, 空队列也需要一个节点. 这和以前的链表同理. 头节点的引用 (指针) 称为 header.
- 入队仅操作尾部, 出队仅操作头部.
一、队列的基本知识
关于队列的详细知识,可以参考拓展部分的博文,这里只摘取比较重要的点简单讲解。
1. 定义
与栈类似,队列(queue)也是一种限制访问点的线性表。队列的元素只能从表的一端插人,另一端删除。按照习惯,通常会把只允许删除的一端称为队列的头,简称队头(font),把删除操作本身称为出队(dequeue);而称表的另一端为队列的尾,简称队尾(rear),这一端只能进行插人操作,称为入队(enqueue)。如同现实生活中的队列一样,在没有人为破坏的前提下,队列是按照到达的顺序来释放元素的,即先进入队列中的元素会先离开,因此队列通常也被称为先进先出(first in frst out)表,简称FIFO表。下图所示为队列的一个示例。售票窗口排队等待买票的人即为队列的一个日常实例。
2. 为什么是用链表来实现?
栈我们使用的顺序表实现的,原因在于顺序表的空间开辟后,进行右侧单项的元素插入非常方便。
但是队列因为是双端开放的,若对顺序表左端进行操作时间的开销并不可观(因为插入一个新元素时,需要将所有元素整体后移一位)。但是,若要坚持使用顺序表完成队列也是可以,就需要对逻辑上的队列出入队操作进行适合于存储结构的优化(这个就是Day 18会介绍的循环队列)。
二、队列的方法
1. 结构定义
由于这里我们采用链表的方式来实现队列,所以在定义队列之前,需要先定义结点。
/**
* An inner class.
*/
class Node {
/**
* The data.
*/
int data;
/*
* The reference to the next node.
*/
Node next;
/**
*******************
* The constructor.
*
* @param paraValue The data.
*******************
*/
public Node(int paraValue) {
data = paraValue;
next = null;
}// Of the constructor
}// Of class Node
在此之后,我们便可以定义队列
/**
* The header of the queue
*/
Node header;
/**
* The tail of the queue.
*/
Node tail;
/**
*********************
* Construct an empty sequential list.
*********************
*/
public LinkedQueue() {
header = new Node(-1);
tail = header;
}// Of the first constructor
/**
*********************
* Overrides the method claimed in Object, the superclass of any class.
*********************
*/
public String toString() {
String resultString = "";
if (header.next == null) {
return "empty";
} // Of if
Node tempNode = header.next;
while (tempNode != null) {
resultString += tempNode.data + ", ";
tempNode = tempNode.next;
} // Of while
return resultString;
}// Of toString
是否设置头结点会令对于首部与空队列的判断出现差异,这里我们设置是的带头结点的单链表,具体差异性不会在本文中过多赘述,具体可以参考以下博文中,对单/双链表的差异性描述:【数据结构】线性表-CSDN博客。
于是本代码的构造函数中预先设置了一个头结点。
2. 入队
/**
*********************
* Enqueue.
*
* @param paraValue The value of the new node.
*********************
*/
public void enqueue(int paraValue) {
Node tempNode = new Node(paraValue);
tail.next = tempNode;
tail = tempNode;
}// Of enqueue
与顺序表队列不同的是,链表队列没有上线这一说法,所以 我们不需要考虑溢出问题。这里这需要构建新元素,并更新指针即可。
3. 出队
public int dequeue() {
if (header == tail) {
System.out.println("No element in the queue");
return -1;
} // Of if
int resultValue = header.next.data;
header.next = header.next.next;
// The queue becomes empty.
if (header.next == null) {
tail = header;
} // Of if
return resultValue;
}// Of dequeu
}// Of enqueue
出队操作需要额外判断队列是否为空,单独声明即可。
除此之外,这里要对以下语句做一个详细解释: header.next = header.next.next;
这个语句的意思是,删除中间一个节点,直接跳向下一个结点。但是我们需要单独考虑下述的一个特殊情况。
如果我们的队列当中只有一个有效结点:
那么自然,我们的rear一定仅仅指向这个结点,若这个时候执行 header.next = header.next.next; 就会无情把front所指的下一个结点删除同时rear也被无情架空了,所以这个时候我们需要重新赋值rear。
重新赋值这个过程可以在删除之前完成,也可以之后完成,这个没有什么影响,这里代码我们使用的是删除之后完成赋值操作:
// The queue becomes empty.
if (header.next == null) {
tail = header;
} // Of if
三、代码及测试
package datastructure.queue;
/**
* Linked queue.
*
* @author: Changyang Hu joe03@foxmail.com
* @date created: 2025-05-16
*/
public class LinkedQueue {
/**
* An inner class.
*/
class Node {
/**
* The data.
*/
int data;
/**
* The reference to the next node.
*/
Node next;
/**
*******************
* The constructor.
*
* @param paraValue The data.
*******************
*/
public Node(int paraValue) {
data = paraValue;
next = null;
}// Of the constructor
}// Of class Node
/**
* The header of the queue.
*/
Node header;
/**
* The tail of the queue.
*/
Node tail;
/**
*
*********************
* @Title: LiknedQueue
* @Description: Construct an empty sequential list.
*
* @return void
*********************
*/
public LinkedQueue() {
header = new Node(-1);
// header.next = null;
tail = header;
}// Of the first constructor
/**
*
*********************
* @Title: enqueue
* @Description: Enqueue.
*
* @param paraValue The value of the new node.
* @return void
*********************
*/
public void enqueue(int paraValue) {
Node tempNode = new Node(paraValue);
tail.next = tempNode;
tail = tempNode;
}// Of enqueue
/**
*
*********************
* @Title: dequeue
* @Description: Dequeue.
*
* @return The value at the header.
* @return int
*********************
*/
public int dequeue() {
if (header == tail) {
System.out.println("No elemnet in the queue");
return -1;
} // Of if
int resultValue = header.next.data;
header.next = header.next.next;
// The queue becomes empty.
if (header.next == null) {
tail = header;
} // Of if
return resultValue;
}// Of dequeue
/**
* Overrides the method claimed in Object, the superclass of any class.
*/
public String toString() {
String resultString = "";
if (header.next == null) {
return "empty";
} // Of if
Node tempNode = header.next;
while (tempNode != null) {
resultString += tempNode.data + ", ";
tempNode = tempNode.next;
} // Of while
return resultString;
}// Of toString
/**
*
*********************
* @Title: main
* @Description: The entrance of the program.
*
* @param args Not used now.
* @return void
*********************
*/
public static void main(String args[]) {
LinkedQueue tempQueue = new LinkedQueue();
System.out.println("Initialized, the list is: " + tempQueue.toString());
for (int i = 0; i < 5; i++) {
tempQueue.enqueue(i + 1);
} // Of for if
System.out.println("Enqueue, the queue is: " + tempQueue.toString());
tempQueue.dequeue();
System.out.println("Dequeue, the queue is: " + tempQueue.toString());
int tempValue;
for (int i = 0; i < 5; i++) {
tempValue = tempQueue.dequeue();
System.out.println("Looped delete " + tempValue + ", the new queue is: " + tempQueue.toString());
} // Of for i
for (int i = 0; i < 3; i++) {
tempQueue.enqueue(i + 10);
} // Of for i
System.out.println("Enqueue, the queue is: " + tempQueue.toString());
}// Of main
}
拓展:
单、双链表:【数据结构】线性表-CSDN博客
小结
队列在计算机中的使用有些时候与栈是一致的,作为常见的受限线性表,栈与队列常常会共同出现。
队列相比于栈的不同最主要在于数据的有序出入特性,就是我们输入的数据顺序是什么样输出就是什么样。
相比于软件,队列这种结构在硬件中使用更多,如我们常见的缓冲区(于数据缓存或者两个不同速度的设备之间,要保证输入输出方的数据必须按序一致),又或者最基本的进程管理(就绪、阻塞队列用来中转下一步处理的进程和尚未获得资源的进程)。
线性结构是计算机底层的结构(地址空间),任何逻辑上的线性结构都能很轻松与物理上的存储结构建立映射或者说抽象,也正因此,线性结构无论是对计算机有关算法、软件、硬件、上层、底层,都是非常重要且基础的结构。