数据结构与算法——单向链表

想知道你的浏览器是否设为无痕浏览呢?那样,你的浏览器就不会有历史记录了👀

咳咳😅,事实上浏览器的历史记录可以使用单向链表来存储用户的浏览记录。每个节点代表一个访问的网页,通过指针连接起来,可以方便地回溯和导航浏览历史。除此之外,生活上还有许多关于链表的应用实例:联系人列表、wechat或者qq用户好友、火车车厢连接。所以,今天的主角是:链表


你们好,我是Benmao

本专栏永久免费,持续更新,喜欢可以收藏,讨厌可以吐槽。

在此专栏我讲不断发表目前学习的数据结构与算法相关笔记,所以不是教程。

用到的语言是Java语言。

此外,向大家推荐学习Java的一个好帮手 笨猫编程手册,遇到有关Java的知识点可以进行查找,对数据结构与算法感兴趣的,可以收藏本栏目


目录

链表引入

单链表介绍

单链表结构实现思路和代码

1. 定义节点属性

2. 初始化头节点

3. 添加节点

4. 遍历链表

5. 测试链表结构 

单链表的增、删、改

1. 单链表的增加

2. 单链表的删除

3. 单链表的更改

链表扩展

1. 反转链表——迭代

2. 反转链表——递归


链表引入

        单链表介绍

        当谈到单链表时,我们通常指的是单向链表(Singly Linked List),它是一种常见的数据结构,用于存储和操作数据。它由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。链表的头节点是第一个节点,尾节点是最后一个节点,尾节点的指针指向null,表示链表的结束。

1) 链表是以节点的方式来存储,是链式存诸

2) 每个节点包含对应的值或其他属性, next属性: 指向下一个节点的地址,
3) 链表的各个节点不一定是连续存储

4)链表分带头节点的链表和没有头节点的,根据实际的需求来确定 

        单向链表的优点是插入和删除节点的时间复杂度为O(1),因为只需要修改指针的指向。然而,访问链表中的特定节点的时间复杂度为O(n),因为需要从头节点开始遍历链表,单链表的节点在内存中可以是分散的,因此在访问链表时,我们需要通过指针来跳转到下一个节点。这也是单链表相对于数组的一个区别,数组的元素在内存中是连续存储的。因此在学习链表或者做一些关于链表的算法题时,基本上会使用辅助变量来完成链表的遍历操作,对于链表很熟悉的小伙伴,对pre、curr、next分别作为当前节点上一个节点、当前节点、当前节点的下一个节点不会感到陌生吧,那么接下来就是实现单链表的思路

         单链表结构实现思路和代码

1. 定义节点属性

        创建一个节点类,包含数据元素和指向下一个节点的指针。节点类可以包含构造函数和其他必要的方法。为了更加直观地感受,除了no(节点不能重复的值)和next,格外地添加了一个属性namenickname来代表创建节点的每一个对应的名称

//定义BenmaoNode类,每一个BenmaoNode对象就是一个节点
class BenmaoNode{
    public int no;//不能重复,用于测验
    public String name;
    public String nickname;
    public BenmaoNode next;//指向下一个节点

    //节点构造器
    public BenmaoNode(int no, String name, String nickname) {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }

    @Override
    public String toString() {
        return "BenmaoNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickname='" + nickname + '\'' +
                '}';
    }
}

 2. 初始化头节点

        新建一个BenmaoLinkedList类,用于实现对链表节点的一些操作。我们先定义一个头节点,不存放具体的数据,后面的遍历操作会更加方便。

private BenmaoNode head = new BenmaoNode(0, "","");
//因为遍历是不使用head,也可以head==null,但之后创建链表时需要判断head是否为null

3. 添加节点

        首先,找到当前链表的最后一个节点。从头节点开始,通过遍历链表,将指针移动到最后一个节点。然后,将最后一个节点的next指针指向新的节点。这样,新节点就被成功添加到了链表的末尾。当我们遍历的时候需要一个辅助变量来完成指针移动的操作,让链表的head的引入赋值给一个节点即可。

//添加节点到单向链表
    //当不考虑编号的顺序时
        //1.找到当前链表的最后节点
        //2.将最后这个节点的next,指向新的节点
    private void add(BenmaoNode node){
        //因为head节点不能动,因此需要一个辅助节点
        BenmaoNode temp = head;
        //遍历链表,找到最后一个节点
        while (true){
            //什么时候到最后? 那就是temp.next==null
            if(temp.next==null){
                break;
            }
            //还不是最后一个节点,就将temp后移
            temp=temp.next;
        }
        //循环结束后,temp已经到达最后一个节点,把最后一个节点的next==null改为next==node
        temp.next=node;
    }

4. 遍历链表

        同样地,若是对上一段代码理解透彻,对于遍历,只需要一个赋值变量。此外,需要注意的是,在开始遍历之前,需要判断一下链表是否为空。

如果链表为空:head.next==null为true
    public void  listAll(){
        //判断链表数据为空,只有head一个无意义的节点
        if(head.next==null){
            System.out.println("链表为空");
            return;
        }
        //因为head节点不能动,因此需要一个辅助节点
        BenmaoNode temp = head.next;
        while (true){
            //判断是否到链表最后
            if(temp==null){
                break;
            }
            //输出节点信息
            System.out.println(temp.toString());
            //将next后移
            temp=temp.next;
        }

    }

5. 测试链表结构 

        在main方法中,依次创建几个节点,把节点串起来(add方法) 就形成了链表。接下来测试一下所写的代码吧

//创建四个节点
BenmaoNode node = new BenmaoNode(1, "Benmao", "笨猫");
BenmaoNode node1 = new BenmaoNode(2, "LBQ", "宝宝");
BenmaoNode node2 = new BenmaoNode(3, "WHY", "IMG");
BenmaoNode node3 = new BenmaoNode(4, "DDG", "DOG");

//把四个节点连起来成为链表
BenmaoLinkedList list = new BenmaoLinkedList();
list.add(node);
list.add(node1);
list.add(node2);
list.add(node3);
//显示链表
list.listAll();

        单链表的增、删、改

1. 单链表的增加

咦,不对吧,之前不是已经写了添加链表节点的方法了吗?难道博主在水博客?

        当然不是,既然有这句话那么一定另有作用。是的,在上文的确我们写过添加链表节点的方法,但是细心的小伙伴就就可以发现,no似乎没有发挥任何作用。所有接下来,将学习如何根据no的大小来添加链表,这样就成为了一个有序链表

         那么思路是什么?

--首先,创建一个辅助节点temp,并将其初始化为头节点。

--遍历链表,找到合适的插入位置。比较当前节点的下一个节点的编号与新节点的编号,直到找到第一个大于新节点编号的节点,或者到达链表的末尾。

--将新节点的next指针指向当前节点的下一个节点,将当前节点的next指针指向新节点。这样,新节点就被成功插入到链表中。

--需要注意的是,在添加节点时,如果发现已经有此节点编号,就不能添加了,因为no是不可重复的。我们可以定义一个flag变量来判断此节点是否存在,只有不存在时才可以添加
    //TODO:按顺序添加节点的方式
    public void addByNo(BenmaoNode node){
        //头节点不能动,通过一个辅助节点来帮助找到添加的位置
        BenmaoNode temp = head;
        boolean flag = false;//添加的编号是否存在,默认false
        while (true){
            if(temp.next==null){//到链表最后
                break;
            }
            if(temp.next.no> node.no){//位置找到了,就在temp后面插入
                break;
            } else if (temp.next.no==node.no) {
                flag = true;//编号存在
                break;
            }
            temp = temp.next;//后移
        }
        //判断flag,如果存在(true),就不能添加
        if(flag){
            System.out.println("节点编号存在,无法添加");
        }else {
            //插入到链表中
            node.next = temp.next;
            temp.next = node;
        }
    }

2. 单链表的删除

        想要删除链表的一个节点,需要找到待删除的节点,因此和单链表的添加类似,如果没有找到此节点一直遍历到链表末尾,那么就不会删除任何一个节点

--首先,创建一个辅助节点temp,并将其初始化为头节点。

--遍历链表,找到需要删除的节点的前一个节点。比较当前节点的下一个节点的编号与目标节点的编号,直到找到目标节点或者到达链表的末尾。

--如果找到目标节点,将前一个节点的next指针指向目标节点的下一个节点,从而跳过目标节点,实现删除操作。

        那么只需要遍历当前节点的下一个节点,意思就是说只需要判断当前节点的no和目标节点的no,因为一旦找到需要删除的节点,只需要把需要删除的节点的上一个节点的next指向需要删除的节点的next即可(可能听起来比较绕,如果这篇博客有人看有人反馈的话我就画图

temp.next.no==no而不是temp.no==no
    //TODO:根据no删除节点
    public void delNode(int no){
        BenmaoNode temp = head;
        boolean flag = false;//是否找到需要删除的节点
        while (true){
            if (temp.next==null){//到达链表的最后
                break;
            }
            if(temp.next.no==no){
                flag=true;
                break;
            }
            temp=temp.next;
        }
        //判断是否找到
        if(flag){
            temp.next=temp.next.next;
        }else {
            System.out.printf("要删除的%d节点不存在", no);
        }
    }

3. 单链表的更改

        如果想要修改链表某个节点的name或者nickname,节点的编号no不能被修改(不用多说了吧,no不可重复),否则会被视为添加新节点。和遍历一样,遍历找到了直接修改属性即可。需要注意的是,如果传递的node的no在链表中找不到,将不会对链表进行任何操作

--首先,创建一个辅助节点temp,并将其初始化为头节点的下一个节点。

--遍历链表,找到需要修改的节点。比较当前节点的编号与目标节点的编号,直到找到目标节点或者到达链表的末尾。

--如果找到目标节点,将目标节点的数据字段更新为新节点的数据字段。
    //TODO:完成修改节点的信息,根据no编号来修改,但是no不能修改,否则就是添加节点
    public void update(BenmaoNode node){//根据node的no来修改
        //是否为空
        if(head.next == null){
            System.out.println("链表为空");
            return;
        }
        BenmaoNode temp = head.next;
        boolean flag = false;//表示是否找到该节点
        while (true){
            if(temp == null){
                break;//快遍历结束了
            }
            if(temp.no == node.no){
                //找到了
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //判断是否找到需要修改的节点
        if(flag){
            temp.name= node.name;;
            temp.nickname = node.nickname;
        }else{
            System.out.printf("没有找到编号为%d的节点\n", node.no);
        }
    }

         链表扩展

1. 反转链表——迭代

        接下来就是关于链表的一些算法的操作,在写算法题中,经常会遇见链表的反转,可能反转所有节点,可能反转部分节点。本篇博客将反转所有节点来讲解反转链表

思路:需要三个变量,先定义一个赋值节点node用于遍历链表,遍历时需要改变当前节点的next改为上一个节点,因此需要其外两个变量res和next分别作为遍历节点的上一个节点、遍历节点的下一个节点,它们分别用于记录上一个节点的地址在和记录下一个节点的地址值,因此可以实现遍历时链表不会断而且改变链表方向的操作
    //TODO:实现反转链表
    public BenmaoNode reserveNode(BenmaoNode node){
        BenmaoNode res = null;//上一个节点
        BenmaoNode curr = node;//当前节点
        BenmaoNode next = null;//保存下一个节点
        while (curr!=null){
            next=curr.next;
            curr.next=res;
            res=curr;
            curr=next;
        }
        return res;
    }

 2. 反转链表——递归

递归算法后面专栏更新,目前当作参考

    //TODO:递归倒序打印链表
    public void printNode(BenmaoNode node){
        if(node==null){
            return;
        }
        printNode(node.next);
        System.out.println(node);
    }

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Benmao⁢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值