java语言实现链队列

简介: 队列是一种特殊的线性表,他的特殊性体现在只能在尾部进行插入,头部进行删除,所以可以将队列看成是一种特殊的线性表。他的鲜明特点就是“先进先出”或者叫“后进后出”。队列这种数据结构的实现一般都是基于线性表的,而线性表又有顺序表和链表两种,所以队列的实现就又分为顺序队列和链队列。这篇文章用以总结使用单链表来实现的队列–链队列。若是需要查看顺序队列实现请点这里: 顺序队列

前言:

队列是一种特殊的线性表,他的特殊性体现在只能在尾部进行插入,头部进行删除,所以可以将队列看成是一种特殊的线性表。他的鲜明特点就是“先进先出”或者叫“后进后出”。队列这种数据结构的实现一般都是基于线性表的,而线性表又有顺序表和链表两种,所以队列的实现就又分为顺序队列和链队列。这篇文章用以总结使用单链表来实现的队列–链队列。若是需要查看顺序队列实现请点这里: 顺序队列


一、实现过程



1.提供队列的接口:IQueue


该接口定义了队列中必须实现的方法,因此在队列实现时只需要继承该接口然后正确实现该接口中的方法即可。无论是顺序队列还是链队列。都可以通过实现该接口来实现,下面是该接口中具体定义的方法。


public interface IQueue {
    void clear();
    boolean isEmpty();
    int length();
    Object peek();//查看队列头元素
    void offer(Object object) throws Exception;//入队
    Object poll();//出队
}


2.提供节点类:Node


因为链队列的底层是单链表,因此我们必须要为单链表提供节点类,节点类需要包含两部分:一部分是数据域用以存储数据;一部分是指针域用以存储指向下一节点的指针。同时需要提供相应的构造方法,代码如下:

public class Node {
    public Object object;
    public Node next;
    public Node(){
    }
    public Node(Object object){
        this.object = object;
    }
    public Node(Node next){
        this.next = next;
    }
    public Node(Object object,Node next){
        this.object = object;
        this.next = next;
    }
    @Override
    public String toString() {
        return "Node{" +
                "object=" + object +
                ", next=" + next +
                '}';
    }
}


3.提供链队列的实现:LinkedQueue


链队列因为数据流向都是单向的,因此使用单链表就可以实现。又根据队列的“先进先出”规则我们可以知道我们需要同时关注首结点和尾结点,因此需要声明尾结点,这样我们在尾部插入时就会很方便,若是不使用尾结点的话,每次队列的插入操作就需要遍历到链表的尾部进行插入,这样每次插入的时间复杂度都变成了O(n),这是很耗费性能的。因此我们需要提供带有尾结点的单链表,同时提供首结点,用以标注链表的开始,注意这里是首结点不是头结点。首结点与头结点的区别是首结点存储数据,头结点是不存储数据的。提供了首结点和尾结点后我们还需要在构造器中对他们进行初始化。代码如下:


/**
 * 链式队列就相当于,既含有首结点,也含有尾结点的单链表
 * @author pcc
 * @version 1.0.0
 * @className LinkedQueue
 * @date 2021-04-29 09:55
 */
public class LinkedQueue implements IQueue{
    Node front;//首结点
    Node rear;//尾结点
    public LinkedQueue(){
        front = rear  = new Node();
    }
    @Override
    public void clear() {
        front = null;
        rear = null;
    }
}


4.提供清空(clear)、判空(isEmpty)、队列长度(length)等方法


这些方法的实现就比较简单,就一起简单说下就好。初始状态下front和rear节点相等且他们的数据域都是null,因此清空时我们就将队列还原成这个样子即可;判空也是判断首结点和尾结点是不是在初始装态,是的话自然就是空的了;队列长度的求取有两种方案,一种是维护一个计数器每次出队和入队时进行计算。另外一种就是遍历链表计算结点个数,笔者使用的是遍历链表,代码实现如下:


    @Override
    public void clear() {
        front = rear  = new Node();
    }
    @Override
    public boolean isEmpty() {
        if(front == rear && front.object == null)
            return true;
        return false;
    }
    @Override
    public int length() {
        int length = 0;
        Node temp = front;
        if(front!=rear)
            while(temp!=null){
                length++;
                temp = temp.next;
            }
        return length;
    }


5.提供入队方法:offer


队列的典型特征是“先进先出”,因此我们肯定是在一端进行插入一端进行删除。简单分析下数据的插入场景后可以发现,在第一个数据插入时front和rear应该还是相等的,只有一个元素时首结点也是尾结点;数据量大于1时则首结点和尾结点则不会有直接关系,此时的插入只需要在尾结点后继续拼接即可,代码如下:

    @Override
    public void offer(Object object) throws Exception {
        Node temp = new Node(object,null);
        if(rear.object==null)
            rear = front = temp;
        else{
            rear.next = temp;//尾部插入
            rear = temp;
        }
    }


6.提供出队方法:poll


队列的典型特征是“先进先出”或者叫“后进后出”,插入时选择在尾部插入,那么我们显然就应该在头部进行删除(反过来其实一样:在链表的头部进行插入尾部进行删除)。头部进行删除我们应该考虑两种情况一种是只有一个元素时一种是多个元素时,一个元素时front和rear相等,此时出队后队列就空了。多个元素时只需要将首结点的下一个结点设置为首结点即可。代码实现如下:

    @Override
    public Object poll() {
        if(front!=null){
            Node frontTemp = front;
            if(front==rear){//只有一个元素时出队
                front = rear = new Node();
            }else{//其他场景出队
                front = front.next;
            }
            return frontTemp.object;
        }
        return null;
    }


7.提供获取队列头部元素的方法:peek


该方法只是返回队列的头部元素,并不进行出队。该方法的实现就比较简单了,我们只需要确定首结点不是null即可,非null返回首结点的数据域,否则返回null。代码如下:

    @Override
    public Object peek() {
        return front!=null?front.object:null;
    }


8.提供实现的完整代码


到这里链队列的方法就都实现了,各个方法实现起来其实都不难,难的是什么呢?好像没有难的,知道链队列的思想就可以轻松实现了。下面贴下链栈的完整代码:

/**
 * 链式队列就相当于,既含有首结点,也含有尾结点的单链表
 * @author pcc
 * @version 1.0.0
 * @className LinkedQueue
 * @date 2021-04-29 09:55
 */
public class LinkedQueue implements IQueue{
    Node front;//首结点
    Node rear;//尾结点
    public LinkedQueue(){
        front = rear  = new Node();
    }
    @Override
    public void clear() {
        front = rear  = new Node();
    }
    @Override
    public boolean isEmpty() {
        if(front == rear && front.object == null)
            return true;
        return false;
    }
    @Override
    public int length() {
        int length = 0;
        Node temp = front;
        if(front!=rear)
            while(temp!=null){
                length++;
                temp = temp.next;
            }
        return length;
    }
    @Override
    public Object peek() {
        return front!=null?front.object:null;
    }
    @Override
    public void offer(Object object) throws Exception {
        Node temp = new Node(object,null);
        if(rear.object==null)
            rear = front = temp;
        else{
            rear.next = temp;//尾部插入
            rear = temp;
        }
    }
    @Override
    public Object poll() {
        if(front!=null){
            Node frontTemp = front;
            if(front==rear){//只有一个元素时出队
                front = rear = new Node();
            }else{//其他场景出队
                front = front.next;
            }
            return frontTemp.object;
        }
        return null;
    }
}


二、测试顺序队列的相应方法



1.测试入队和出队


链队列的实现已经完成了,下面需要测试下实现的方法是否正确,根据队列的“先进先出”特点,若是入队和出队的顺序保持一致,则说明方法的实现没有问题,测试结果如下,可以发现方法的入队和出队是一致的,也代表队列的入队和出队方法没有问题。


20210513104836577.gif


2.测试其他方法


上面的案例中已经测试了入队、出队、长度、非空等方法了,下面再配合清空方法测试下,如下图:

image.gif


由上图可以看出:插入5个元素后队列长度是5、此时判空是false、然后清空操作再判空是true、然后获取首元素就是null;可以发现这些方法的操作和输出都没有问题。


三、总结



这篇文章总结了链队列的实现,代码是没有难度的,主要是掌握队列的思想。无论是使用单链表还是顺序表来实现队列,都只是队列的内部实现而已,平时的使用更关注的是队列的操作。队列是一种使用很广的数据结构,使用频率也很高,比如jvm中的finalize队列,线程池的等待队列,以及各种阻塞非阻塞队列等等。所以说掌握队列的思想还是很有必要的,这篇文章只是以一个实现者的角度去写的,可能并不是适合很多人,但也希望可以帮到看到这篇文章的你。


相关文章
|
1月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
69 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
3月前
|
Java Maven
使用java语言制作一个窗体(弹窗),用来收集用户输入的内容
该博客文章介绍了如何使用Java Swing中的JFrame创建一个窗体来收集用户输入的内容,并提供了详细的实现步骤和完整代码示例。
使用java语言制作一个窗体(弹窗),用来收集用户输入的内容
|
11天前
|
SQL 安全 Java
安全问题已经成为软件开发中不可忽视的重要议题。对于使用Java语言开发的应用程序来说,安全性更是至关重要
在当今网络环境下,Java应用的安全性至关重要。本文深入探讨了Java安全编程的最佳实践,包括代码审查、输入验证、输出编码、访问控制和加密技术等,帮助开发者构建安全可靠的应用。通过掌握相关技术和工具,开发者可以有效防范安全威胁,确保应用的安全性。
25 4
|
1月前
|
Java 程序员 编译器
在Java编程中,保留字(如class、int、for等)是具有特定语法意义的预定义词汇,被语言本身占用,不能用作变量名、方法名或类名。
在Java编程中,保留字(如class、int、for等)是具有特定语法意义的预定义词汇,被语言本身占用,不能用作变量名、方法名或类名。本文通过示例详细解析了保留字的定义、作用及与自定义标识符的区别,帮助开发者避免因误用保留字而导致的编译错误,确保代码的正确性和可读性。
43 3
|
1月前
|
移动开发 Java 大数据
深入探索Java语言的核心优势与现代应用实践
【10月更文挑战第10天】深入探索Java语言的核心优势与现代应用实践
51 4
|
1月前
|
存储 安全 Java
【用Java学习数据结构系列】探索栈和队列的无尽秘密
【用Java学习数据结构系列】探索栈和队列的无尽秘密
30 2
|
1月前
|
存储 Java 数据安全/隐私保护
Java中的域,什么是域?计算机语言中的域是什么?(有代码实例)
文章解释了Java中域的概念,包括实例域、静态域、常量域和局部域,以及它们的特点和使用场景。
57 2
|
1月前
|
Java 数据安全/隐私保护 C++
Java语言关键字
Java语言关键字
22 2
|
2月前
|
Java API 容器
JAVA并发编程系列(10)Condition条件队列-并发协作者
本文通过一线大厂面试真题,模拟消费者-生产者的场景,通过简洁的代码演示,帮助读者快速理解并复用。文章还详细解释了Condition与Object.wait()、notify()的区别,并探讨了Condition的核心原理及其实现机制。
|
1月前
|
分布式计算 安全 Java
Java语言的特点?
Java语言的特点?