【大厂算法系列】链表实战篇,基于链表编码实现课程信息管理系统

本文通过链表实现课程信息管理系统,涵盖单向链表的增加、删除、修改及查找倒数第K个课程,进一步探讨双向链表的操作。讨论了链表与顺序表在增删查改上的优缺点,分析了操作的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述


- 顺序表遗留的问题

  • 前面我们使用顺序储存结构实现的顺序表,虽然查询的时候很快,同时也存在一些问题。
  • 问题1:在进行元素的增加或者删除的时候:比较麻烦,需要你去移动大量的元素把数据删除或者增加。
  • 问题2:查找速度快,但是插入速率低

- 链表

  • 链表里的数据是以结点方式来表示的,每一个结点的组成是由:元素+指针来组成的,元素就是存储数据里的存储单元,指针就是用来连接每一个结点的地址数据。这个以结点的序列来表示线性表被称作为单链表
  • 单链表是一种链式储存结构。在物理储存单元不连续,非顺序。
    • 结点里的数据域是用来存储数据元素的
    • 指针是用于指向下一个具有相同结构的结点
    • 因为只有一个指针结点,故而被称为单链表。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AfxPPYon-1679329108753)(picture/image-20221018110320319.png)]

- 单向链表

  • 单向链表是链表中的其中一种,它是由多个结点组成的,每一个结点都是有一个数据域+指针域组成。
  • 数据域是用来存储数据元素的,指针用来指向后面的节点
  • 链表分成带头结点的链表跟不带头结点的链表。
  • 单链表的头节点里不储存数据,它的指针指向真正存储有数据的第一个链表节点。
  • 带头结点的单链表演示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M7arzcHZ-1679329108753)(picture/image-20221020111643659.png)]

- 链表储存地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tBqgAeyv-1679329108754)(picture/image-20221020111457674.png)]

单向链表的增加与遍历节点演示
  • 需要实现的方法
课程信息节点类class CourseNode{}
添加课程到链表进行管理public void addCourse(CourseNode node)
删除链表中的课程 传入课程idpublic void delCourse(int id)
修改对应课程的信息public void update(CourseNode node)
遍历输出课程public void showCourse()
获取链表中课程的数量public int getLength()
查找倒数第K个课程信息public CourseNode getLastNum(int K)

- 单向链表的增加与遍历

  • 增加

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uHfGnk6A-1679329108754)(picture/image-20221020111725315.png)]

  • 遍历输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yHJpHkW6-1679329108755)(picture/image-20221020112302599.png)]

public void addCourse(CourseNode node){
         //辅助的指针
        CourseNode cur=head;
        //不断的遍历链表 找到最后一个节点
        while (true){
            //找到了
            if(cur.next==null){
                break;
            }
            //辅助指针往下移动
            cur=cur.next;
        }
        cur.next=node;
        length++;

    }
    //遍历输出课程
    public void showCourse(){
        //定义辅助的指针
        CourseNode cur=head;
        if(cur.next==null){
            System.out.println("链表空 不可以输出");
            return;
        }
        while (true){
            if(cur.next==null){
                System.out.println("课程输出完成");
                return;
            }
            System.out.println(cur.next);
            cur=cur.next;
        }
    }
链表之单向链表实现删除与修改节点
  • 单链表删除节点示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4u49pLGj-1679329108755)(picture/image-20221020111808786.png)]

- 单链表修改节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HdPNSfYX-1679329108755)(picture/image-20221020120718085.png)]

 //修改课程信息
    public void update(CourseNode node){
        CourseNode cur=head;
        boolean flag=false;
        while (cur.next!=null){
            if(cur.next.id==node.id){
                flag=true;
                break;
            }
            cur=cur.next;
        }
        if(flag){
            cur.next.id=node.id;
            cur.next.courseName=node.courseName;
        }else {
            System.out.println("没有找到要修改的课程");
        }
    }
链表实现查找倒数第K个课程
  • 思路分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rU7589cH-1679329108756)(picture/image-20221020121041503.png)]

public CourseNode getLastCourse(int k){
        CourseNode cur=head;
        if(cur.next==null){
            System.out.println("链表空 不可以返回");
            return null;
        }
        int length=getLength();
        if(k==0 || k>length){
            throw new IllegalArgumentException("参数错误");
        }
        System.out.println("链表长度:"+length);
        for(int i=0;i<length-k+1;i++){
        //for(int i=0;i<=length-k;i++){
            cur=cur.next;
        }
        return cur;
    }
链表进阶之实现双向链表的增加与遍历
  • 什么是双向链表

  • 双向链表也被称作为双向表,它和单向链表一样,属于链表的一种。

  • 有多个结点来组成的,和单向链表不一样的是,它拥有两个指针域,一个指向后继的节点,另一个指向前驱结点。

  • 链表的头结点的数据域是不存储数据的,所以,头结点的前驱指针域为null,它的后继指针域指的是第一个真正有数据存储的结点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nQqWcoVq-1679329108756)(picture/image-20221027141500529.png)]

  • 双向链表的增加和遍历

  • 增加

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NSZmY1ZC-1679329108757)(picture/image-20221027141515814.png)]

  • 遍历
//添加课程
    public void addCourse(CourseNode2 node){
        //辅助的指针
        CourseNode2 cur=head;
        //不断的遍历链表 找到最后一个节点
        while (true){
            //找到了
            if(cur.next==null){
                break;
            }
            //辅助指针往下移动
            cur=cur.next;
        }
        cur.next=node;
        node.pre=cur;
        length++;

    }
双向链表实战删除与修改课程
  • 双向链表删除节点思路分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qgVWeZZZ-1679329108757)(picture/image-20221027161343270.png)]

  • 双向链表修改课程信息(同单链表类似)
 //删除课程 根据课程的id
    public void delCourse(int id){
        //定义辅助变量
        CourseNode2 cur=head.next;
        if(cur==null){
            return;
        }
        //记录是否找到课程
        boolean flag=false;
        while (cur!=null){
            if(cur.id==id){
                flag=true;
                break;
            }
            //一直遍历
            cur=cur.next;
        }
        if(flag){
            //删除节点
            // data1 next->data3
            cur.pre.next=cur.next;
            //data3 pre->data1
            if(cur.next!=null){
                cur.next.pre=cur.pre;
            }
            length--;
        }else {
            System.out.printf("要删除的节点%d没有找到",id);
        }
    }
  • 对单链表与双链表的相关操作进行时间复杂度分析

  • 删除节点时

    • 单链表删除节点的时候,需要找到前驱节点,也就是通过遍历的方式得到。单链表的增删操作复杂度是O(n).
    • 双向链表删除节点的时候,因为是双向的,直接通过pre就可以得到前驱节点,不用进行遍历,时间复杂度是O(1)
    • 如果只知道要删除的双链表节点的序号,那一样要遍历,时间复杂度是O(n)
  • 插入节点时

    • 单链表与双链表的插入都要找到前驱节点,需要遍历找到的话时间复杂度就是O(n),给了前驱节点复杂度就是O(1)
  • 查找节点时

    • 查找时,时间复杂度均为O(n)
  • 总体代码

  • 单链表:

public class SingleLinkeDemo {
    public static void main(String[] args) {
        CourseLinke courseLinke = new CourseLinke();
        CourseNode node1 = new CourseNode(1, "数据结构与算法");
        CourseNode node2 = new CourseNode(2, "数据结构与算法");
        CourseNode node3 = new CourseNode(3, "数据结构与算法");
        CourseNode node4 = new CourseNode(4, "数据结构与算法");
        CourseNode node5 = new CourseNode(5, "数据结构与算法");
        courseLinke.addCourse(node1);
        courseLinke.addCourse(node2);
        courseLinke.addCourse(node3);
        courseLinke.addCourse(node4);
        courseLinke.showCourse();
//        System.out.println("   ====   ");
//        courseLinke.delCourse(1);
//        System.out.println("删除课程id=1的课程后");
//        courseLinke.showCourse();
//        System.out.println("==修改信息后==");
//        courseLinke.update(node4);
//        courseLinke.showCourse();
        courseLinke.addNodeIndex(3,node5);
        System.out.println("  ====  ");
        System.out.println("倒数第1门课程的信息是:"+courseLinke.getLastCourse(1));
        courseLinke.showCourse();
    }
}
//链表类  管理课程
class CourseLinke{
    //头节点 初始化
    public CourseNode head=new CourseNode(0,"");
    //链表有效元素个数
    private int length=0;
    //添加课程
    public void addCourse(CourseNode node){
         //辅助的指针
        CourseNode cur=head;
        //不断的遍历链表 找到最后一个节点
        while (true){
            //找到了
            if(cur.next==null){
                break;
            }
            //辅助指针往下移动
            cur=cur.next;
        }
        cur.next=node;
        length++;

    }
    //指定位置插入  index=2,即在第二个元素之前插入
    public void addNodeIndex(int index,CourseNode node){
        //辅助指针
        CourseNode cur=head;
        //判断参数是否合法
        if(index>length || index<0) return;
        for(int i=1;i<index;i++){
            cur=cur.next;
        }
        System.out.println("当前cur"+cur);
        CourseNode temp=cur.next;
        cur.next=node;
        node.next=temp;
    }
    //修改课程信息
    public void update(CourseNode node){
        CourseNode cur=head;
        boolean flag=false;
        while (cur.next!=null){
            if(cur.next.id==node.id){
                flag=true;
                break;
            }
            cur=cur.next;
        }
        if(flag){
            cur.next.id=node.id;
            cur.next.courseName=node.courseName;
        }else {
            System.out.println("没有找到要修改的课程");
        }
    }
    //删除课程 根据课程的id
    public void delCourse(int id){
        //定义辅助变量
        CourseNode cur=head;
        if(cur.next==null){
            return;
        }
        //记录是否找到课程
        boolean flag=false;
        while (cur.next!=null){
            if(cur.next.id==id){
                flag=true;
                break;
            }
            //一直遍历
            cur=cur.next;
        }
        if(flag){
            //删除节点
            cur.next=cur.next.next;
            length--;
        }else {
            System.out.printf("要删除的节点%d没有找到",id);
        }
    }
    //遍历输出课程
    public void showCourse(){
        //定义辅助的指针
        CourseNode cur=head;
        if(cur.next==null){
            System.out.println("链表空 不可以输出");
            return;
        }
        while (true){
            if(cur.next==null){
                System.out.println("课程输出完成");
                return;
            }
            System.out.println(cur.next);
            cur=cur.next;
        }
    }
    //获取倒数第K个节点  1.获取到链表长度  2.遍历链表直到(链表长度-K+1) return node
    public CourseNode getLastCourse(int k){
        CourseNode cur=head;
        if(cur.next==null){
            System.out.println("链表空 不可以返回");
            return null;
        }
        int length=getLength();
        if(k==0 || k>length){
            throw new IllegalArgumentException("参数错误");
        }
        System.out.println("链表长度:"+length);
        for(int i=0;i<length-k+1;i++){
            cur=cur.next;
        }
        return cur;
    }
    //获取链表长度
    public int getLength(){
        return length;
    }
}
class CourseNode{
    public int id;
    public String courseName;
    public CourseNode next;
    public CourseNode(int id,String courseName){
        this.id=id;
        this.courseName=courseName;
    }

    @Override
    public String toString() {
        return "CourseNode{" +
                "id=" + id +
                ", courseName='" + courseName + '\'' +
                ", next=" + next +
                '}';
    }
}

双链表

public class DoubleLinkeDemo {
    public static void main(String[] args) {
        DoubleLinke doubleLinke = new DoubleLinke();
        CourseNode2 node1 = new CourseNode2(1, "数据结构与算法课程1");
        CourseNode2 node2 = new CourseNode2(2, "数据结构与算法课程1");
        CourseNode2 node3 = new CourseNode2(3, "数据结构与算法课程1");
        CourseNode2 node4 = new CourseNode2(3, "数据结构与算法课程5");
        doubleLinke.addCourse(node1);
        doubleLinke.addCourse(node2);
        doubleLinke.addCourse(node3);
        doubleLinke.delCourse(1);
        doubleLinke.update(node4);
        doubleLinke.showCourse();
    }
}

//链表类  管理课程
class DoubleLinke{
    //头节点 初始化
    public CourseNode2 head=new CourseNode2(0,"");
    //链表有效元素个数
    private int length=0;
    //添加课程
    public void addCourse(CourseNode2 node){
        //辅助的指针
        CourseNode2 cur=head;
        //不断的遍历链表 找到最后一个节点
        while (true){
            //找到了
            if(cur.next==null){
                break;
            }
            //辅助指针往下移动
            cur=cur.next;
        }
        cur.next=node;
        node.pre=cur;
        length++;

    }
    //修改课程信息
    public void update(CourseNode2 node){
        CourseNode2 cur=head;
        boolean flag=false;
        while (cur.next!=null){
            if(cur.next.id==node.id){
                flag=true;
                break;
            }
            cur=cur.next;
        }
        if(flag){
            cur.next.id=node.id;
            cur.next.courseName=node.courseName;
        }else {
            System.out.println("没有找到要修改的课程");
        }
    }
    //删除课程 根据课程的id
    public void delCourse(int id){
        //定义辅助变量
        CourseNode2 cur=head.next;
        if(cur==null){
            return;
        }
        //记录是否找到课程
        boolean flag=false;
        while (cur!=null){
            if(cur.id==id){
                flag=true;
                break;
            }
            //一直遍历
            cur=cur.next;
        }
        if(flag){
            //删除节点
            // data1 next->data3
            cur.pre.next=cur.next;
            //data3 pre->data1
            if(cur.next!=null){
                cur.next.pre=cur.pre;
            }
            length--;
        }else {
            System.out.printf("要删除的节点%d没有找到",id);
        }
    }
    //遍历输出课程
    public void showCourse(){
        //定义辅助的指针
        CourseNode2 cur=head;
        if(cur.next==null){
            System.out.println("链表空 不可以输出");
            return;
        }
        while (true){
            if(cur.next==null){
                System.out.println("课程输出完成");
                return;
            }
            System.out.println(cur.next);
            cur=cur.next;
        }
    }
    //获取倒数第K个节点  1.获取到链表长度  2.遍历链表直到(链表长度-K) return node
    public CourseNode2 getLastCourse(int k){
        CourseNode2 cur=head;
        if(cur.next==null){
            System.out.println("链表空 不可以返回");
            return null;
        }
        int length=getLength();
        for(int i=0;i<length-k;i++){
            cur=cur.next;
        }
        return cur;
    }
    //获取链表长度
    public int getLength(){
        return length;
    }
}

class CourseNode2{
    public int id;
    public String courseName;
    public CourseNode2 next;
    //前驱节点
    public CourseNode2 pre;
    public CourseNode2(int id,String courseName){
        this.id=id;
        this.courseName=courseName;
    }
//    这两个方法都会尝试在它们的邻居上调用toString(),包括你开始时使用的Node。这是一个无限递归。
//    要避免这种情况,请不要在节点的toString()方法中包含相邻节点的字符串表示。
    @Override
    public String toString() {
        return  "课程id=" + id +
                ", courseName='" + courseName + '\'' +
                ", next=" + next +
                '}';
    }
}

专栏地址:【大厂算法系列】
专栏内容: 数组,列表,栈,队列,树,哈希表,字符串,堆,查找,排序,DFS,BFS,回溯,贪心,动态规划等+力扣大厂真题
算法交流: V 【yopa66】

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大数据小禅

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

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

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

打赏作者

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

抵扣说明:

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

余额充值