一篇讲完Java链表(单链表、循环链表和双向链表)

一篇总结Java链表(单链表、循环链表和双向链表)

什么是Java链表?链表,是Java中的一种重要的链式数据结构。
众所周知,我们日常用手机电脑收发信息,下载视频和软件,都要进行数据的传输。这些数据都要以一种特定的数据结构来进行传输和存储,否则数据将会是一串毫无意义的0和1,无法生成我们想要的文件。一般来说有两种最常见的基本数据结构,一种是线性结构的数组,一种是链式结构的链表。有关数组的内容会在另一篇文章中介绍,这里我们主要来介绍一下链表的内容。
链表
链表(Linked list)是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。所谓链表,在存储器中不是一条连续的数据段,而是一群通过指针首尾相连的数据节点的集合。每一个节点包含一部分数据,并且包含指向其他节点的指针。通过这些指针,这些存储在不同物理位置的、包含数据的节点连在了一起,形成了一个像是锁链一样的链表。相比而言,链表是比数组更加方便的数据结构。这主要体现在链表和数组的数据增加和删除方面。当增加或删除数据时,数组需要将特定数据增删后创建一个新的数组,并将修改后的数组复制进去;而链表则只需要改动需要增删的数据的前后指针就可以实现数据的修改,不需要变动整个链表。可以看看下面这个图,说明了两者的区别。
在这里插入图片描述

节点
上面我们说过,链表是由一连串的节点构成的,而节点是由数据和指针构成的。通过指针的变化和连接,可以将众多的节点串联起来,并且可以形成很多不同类型的链表。常见的链表有单链表,循环链表,双向链表和双向循环链表。

单链表
单链表是较为简单的链表,单链表的指针指向同一方向的下一个元素。在单链表中,有两个特殊的节点,头节点和尾节点。头节点(也叫根节点)是单链表中的第一个节点,是单链表中的关键节点。通过头节点,我们可以将指针指向头节点,遍历整个单链表,来实现增删改查等操作。这样操作可以防止链表为空(链表若为空,则头节点指针域为null),并且可以保持对单链表操作的统一性(从头节点开始遍历),简化操作减少bug。尾节点是单链表中的最后一个节点,它的指针指向一个空的地址null(没有下一个节点了)。在单链表的操作中,我们可以通过定义尾节点的指针来实现不同的方法。
在这里插入图片描述

循环链表
循环链表的结构与单链表类似,不同之处在于单链表的尾节点指向一个null空地址,而循环链表的尾节点指针指向了头节点。这样,就形成了一个首尾相连的环形结构的链表。循环链表适合用来处理环形结构的数据。
在这里插入图片描述

双向链表
双向链表是一种可以双向遍历的链表,它的指针可以指向前后两个方向的节点。双向链表拥有头节点和尾节点,可以从头节点进入链表进行操作。与单链表不同的是,双向链表拥有两个指针地址,多了一个指向前一节点的引用地址。双向链表的优点是,在操作链表中的上一节点时可以通过前指针直接向前遍历,而无需从头节点开始重新遍历,提高了链表的效率。其缺点是比单链表结构多了一个指针地址,会占用更多的内存来进行存储。
在这里插入图片描述

双向循环链表
双向循环链表是循环链表与双向链表的结合,每个节点拥有前后指针,并且尾节点指向头节点形成循环。可以用来灵活处理环形结构的数据。
在这里插入图片描述

单链表的代码实现
在了解了单链表的原理后,我们来通过代码实现单链表的增删改查功能,以及反转链表和输出链表。
创建链表类及节点
在单链表中,我们首先要定义链表中最基础的节点元素。如上图所示,节点中包括两个属性,指针next和数据data。同时,我们可以在创建时定义链表的头节点、尾节点和链表长度。

public class LinkList {
    public Node root = null; //定义头节点,默认为空
    public Node tail;  //定义尾节点
    public int length; //定义链表长度
    public class Node{ //声明节点类
        public int data; //定义数据对象
        public Node next = null; //定义指针,默认为空
        public Node(int data) {this.data = data; } //重写构造方法,初始化节点
}

添加节点
实现添加节点功能,我们主要有两种思路:头部添加和尾部添加。两种处理方式大同小异,但最终生成的链表顺序会不同。首先要排除头节点为空的情况,然后基于头节点来进行数据的添加。
尾部添加
尾部添加/顺序插入/有头插入都是同一个功能,是指将链表中插入的第一个元素设置为头节点,并且将之后添加的元素依次置于头节点之后,形成一个正顺序的链表。插入的第一个节点为头节点,新插入的节点为尾节点。如下图所示,1、2、3分别是插入节点的次序。
在这里插入图片描述
代码实现如下。

public void addTail(int x){
    Node newNode = new Node(x); //定义新节点
    if (root == null){ //若头节点为空,则头节点为新节点
        root = newNode;
        tail = newNode;
    }else{
        tail.next = newNode;//新节点指向尾节点后续
        tail = newNode;
    }
    length++;//顺手统计一下链表长度……
}

头部添加
与尾部添加相反,头部添加是每次添加新数据时将新添加的数据作为头部节点,使得新节点一直处于链表头部。通过头部添加的链表,生成的数据是逆序的。下图是添加的逻辑。
在这里插入图片描述
代码实现如下。

public void addHead(int x){
    Node newNode = new Node(x);
    if (root == null){
        root = newNode;
    }else{
        newNode.next = root;// 将新节点的指针指向头节点
        root = newNode; //将新数据赋给头节点
    }	
    length++;
}

获取链表长度
在添加节点方法时,我们已经递增length变量得到了链表的长度,但我们还需要定义一个方法来获得链表的长度。

public int getLength(){
    return length;}

删除节点
删除节点的逻辑:将待删除节点的前一节点的指针指向待删除节点的后一节点,跳过当前待删除节点,使其自动被内存回收,实现节点删除。看图。
在这里插入图片描述

接下来我们用代码来实现。实现删除时要考虑到删除头节点的情况,可以直接将头节点指向下一节点来跳过头节点并返回来删除它。代码如下。

public boolean delete(int x){
    if(x<0||x>length-1){//排除不合法删除
        System.out.println("删除错误,超出链表范围");
        return false;
    }
    if(x == 0){//删除头节点时,头节点指向下一节点
        root = root.next;
        length--;
        return true;
    }
    Node preNode = root;//指定头节点为前节点
    Node curNode = preNode.next;//声明当前待删除节点
    int i = 1;
    while (curNode != null) {
        if (i == x) {//寻找待删除节点,遍历直到i=输入值
            preNode.next = curNode.next; //待删除节点的前节点指向后节点
            length--;//链表长度-1
        }
        preNode = preNode.next;//当前节点和前节点同时后移
        curNode = curNode.next;
        i++;
    }
    return true;
}

根据下标查找节点
使用for循环遍历链表,直到指定位置后停止循环,输出当前节点的数据。

public int searchByOrder(int x){
    Node curNode = root;
    if(x<0 || x>length){
        System.out.println("输入错误,超出链表限值");
        return 000;
    }
    for (int i=1;i<=x-1;i++){
        curNode = curNode.next;
    }
    return curNode.data;
}

根据内容查找节点
使用while循环遍历链表,找到相同内容的节点后返回true,表示链表中存在该数据。

public boolean searchByContent(int x){
    Node curNode = root;
        while (curNode != null) {
            if (curNode.data == x) {
                return true; //数据相同时返回true
            } else {
                curNode = curNode.next; //否则继续寻找
            }
        }return false; //要注意在while循环外返回false
}

查询具体数据在链表中的节点数量
与上一个方法相似,在while循环遍历中增加了一个变量,在找到指定内容时作为统计数据返回。并且继续遍历,直到遍历完整个链表为止。

public int SearchAndCount(int x){
    Node curNode = root;
    int i = 0;//统计节点数量
    while (curNode != null){
        if(curNode.data == x){
            i++;//符合条件的节点+1
            curNode = curNode.next;//继续循环
        }else{
            curNode = curNode.next;如果不符合也要循环
        }
    }return i;//将统计值返回
}

反转链表
链表反转的主要逻辑是将指针反转并将节点后移,遍历后实现反转。需要注意的是,在反转指针前需要保存下一节点,以免指针反转后指空。代码如下。

public void reverse() {
    Node curNode = root;
    Node preNode = null;
    while (curNode != null){
        Node nextNode = curNode.next; //保存下一节点
        curNode.next = preNode;//指针反转
        preNode = curNode; //前节点后移
        curNode = nextNode;//现节点后移
    }
    root = preNode;
}

打印链表
链表打印就比较简单了,将链表从头遍历并打印出来。

public void printLink(){
    Node curNode = root;
    while (curNode != null){
            System.out.print(curNode.data+" ");
            curNode = curNode.next;
        }
        System.out.println();
}

双向链表和循环链表的代码实现与单链表有些不同,但我懒得写了……过段时间再更新吧~

  • 7
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值