学习链表必备的1w个技巧Java版本

2023年 10月 1日 89.8k 0

链表(Java版本)

关于作者

  • 作者介绍

🍓 博客主页:作者主页
🍓 简介:JAVA领域优质创作者🥇、一名初入职场小白🎓、曾在校期间参加各种省赛、国赛,斩获一系列荣誉🏆
🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻

简介

链表是有序的列表,但是它在内存中是存储如下

image-20220226134528249

​ 1.链表是以节点的方式来存储,是链式存储

​ 2.每个节点包含 data 域, next 域:指向下一个节点.

​ 3.发现链表的各个节点不一定是连续存储

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

单链表(带头节点)

image-20220226134725293

实例

使用带 head 头的单向链表实现—学生信息管理完成对学生信息的增删改查操作

思路

添加

直接添加到链表的尾部

image-20220226140519063

在添加信息时,根据编号将学生信息插入到指定位置(如果存在这个编号,则添加失败,并给出提示

思路的分析示意图:

image-20220226140847849

修改

修改节点的信息, 根据 no 编号来修改,即 no 编号不能改.

  • 根据 newHeroNode 的 no 来修改即可
  • 说明我们在比较时,需要查找当前节点是否存在 如果存在才能进行修改
  • 删除

  • head 不能动,因此我们需要一个 temp 辅助节点找到待删除节点的前一个节点
  • 说明我们在比较时,是 temp.next.sno 和 需要删除的节点的 sno 比较
  • image-20220226141110446

    代码

    package com.cz.LinkedList;
    
    import java.util.Stack;
    
    /**
     * @ProjectName: Data_structure
     * @Package: com.cz.LinkedList
     * @ClassName: SingleLinkedListDemo
     * @Author: 张晟睿
     * @Date: 2022/2/24 10:32
     * @Version: 1.0
     */
    public class SingleLinkedListDemo {
        public static void main(String []args){
            NodeList list = new NodeList(0,"zrq",20,"软工1班");
            NodeList list1 = new NodeList(1,"zrq1",21,"软工2班");
    //        NodeList list2 = new NodeList(2,"zrq2",22,"软工3班");
    //        NodeList list3 = new NodeList(3,"zrq3",23,"软工4班");
            NodeList list4 = new NodeList(2,"zrq4",24,"软工5班");
            NodeList list5 = new NodeList(5,"sss",21,"软工1班");
    
            SingleLinkedList singleLinkedList1 = new SingleLinkedList();
            SingleLinkedList singleLinkedList2 = new SingleLinkedList();
            singleLinkedList1.add(list);
            singleLinkedList1.add(list1);
    //        singleLinkedList1.addByOrder(list2);
    //        singleLinkedList1.addByOrder(list3);
    
            singleLinkedList2.add(list4);
            singleLinkedList2.add(list5);
            singleLinkedList2.list();
            System.out.println("+++++");
            System.out.println(list4.next);
            System.out.println("+++++");
    
    //        singleLinkedList.reversalNode(singleLinkedList.getHead());
    //
    //        singleLinkedList.list();
    //        singleLinkedList.reversePrint(singleLinkedList.getHead());
    //        singleLinkedList.add(list4);
    
    //        singleLinkedList.delete(list4.sno);
    //
    //        singleLinkedList.update(list5);
    //
    //        singleLinkedList.list();
    //
    //        System.out.println("链表的有效数据个数为:"+ singleLinkedList.size());
    //
    //        //测试查找单链表中倒数第k个节点
    //        NodeList lastIndexNode = singleLinkedList.findLastIndexNode(singleLinkedList.getHead(), 4);
    //        System.out.println(lastIndexNode);
    
    //        singleLinkedList.reversalNode(singleLinkedList.getHead());
    
            singleLinkedList1.mergeNode(singleLinkedList1.getHead(), singleLinkedList2.getHead());
        }
    }
    //管理Node节点
    class SingleLinkedList{
        //初始化头节点
        private NodeList head = new NodeList(0,"",0,"");
    
        public void setHead(NodeList head) {
            this.head = head;
        }
    
        public NodeList getHead() {
            return head;
        }
    
        //统计有效的节点个数
        public int size(){
            //链表为空 则返回长度0
            if (head.next == null) return 0;
            //定义一个辅助来记录当前的节点
            NodeList temp = head.next;
            int count = 0;//统计节点个数
            while(temp != null){
                count++;
                temp = temp.next;
            }
            return count;
        }
        //向节点中添加数据
        //思路,当不考虑编号顺序时
        //1. 找到当前链表的最后节点
        //2. 将最后这个节点的 next 指向 新的节
        public void add(NodeList nodeList){
            NodeList temp = head;
            while(true){
                if (temp.next == null){
                    break;
                }
                temp = temp.next;
            }
            //退出循环时,链表指向最后
            temp.next = nodeList;
        }
    
    
    
    
        //向节点中添加数据并排序
        //思路,当考虑编号顺序时
        //1. 找到当前链表与当前链表的后一个节点的sno编号进行比较
        //2. 将最后这个节点的 next 指向 新的节
        public void addByOrder(NodeList nodeList){
            boolean flag = false;//判断是否已经存在该排名
            NodeList temp = head;
            while(true) {
                //判断是否到达链表尾部
                if (temp.next == null) {
                    break;
                }
                //判断插入的节点与后一个节点的sno的大小
                if (temp.next.sno > nodeList.sno) {
                    break;//此时找到插入的位置在temp的后面
                } else if (temp.next.sno == nodeList.sno) {
                    flag = true; //说明编号存在
                    break;
                }
                temp = temp.next;
            }
                if (flag) {
                    System.out.println("该编号已存在,请重新输入");
                } else {
                    nodeList.next = temp.next;
                    temp.next = nodeList;
                }
        }
        //显示节点中的所有对象
        public void list(){
            //判断节点是否为空
            if(head.next == null){
                return;
            }
            NodeList temp = head.next;
            while(true){
                if (temp == null){
                    break;
                }
                System.out.println(temp);
                temp = temp.next;
            }
        }
    
    
        //修改节点的信息, 根据 no 编号来修改,即 no 编号不能改.
        //1. 根据 newHeroNode 的 no 来修改即可
        //2. 说明我们在比较时,需要查找当前节点是否存在 如果存在才能进行修改
        public void update(NodeList nodeList){
            //标记是否找到该节点
            int flag = 0;
            //头节点不能动
            if (head.next == null) return ;//链表是否为空
            NodeList temp = head.next;
            while(true) {
                if (temp == null)//判断是否到达链表尾部
                {
                    break;
                }
                if (nodeList.sno == temp.sno) {
                    //找到该节点
                    flag = 1;
                    break;
                }
                temp = temp.next;
            }
                if (flag == 1){
                    temp.setSno(nodeList.sno);
                    temp.setName(nodeList.name);
                    temp.setAge(nodeList.age);
                    temp.setGrade(nodeList.grade);
                }else
                    System.out.printf("要修改的 %d 节点不存在,无发修改n", nodeList.sno);
        }
    
    //    删除节点
    //    思路
    //    1. head 不能动,因此我们需要一个 temp 辅助节点找到待删除节点的前一个节点
    //    2. 说明我们在比较时,是 temp.next.sno 和 需要删除的节点的 sno 比较
        public void delete(int sno){
            //用来标记是否找到该节点 0是为找到  1为找到
            int flag = 0;
            //头节点不能动
            NodeList temp = head;
            //判断节点是否为空
            if(head.next == null){
                return;
            }
            while(true){
                //判断节点是否到达末尾
                if (temp.next == null){
                    break;
                }
                //删除的链表号是否被找到
                if (temp.next.sno == sno) {
                    flag = 1;break;
                }
                temp = temp.next;//当前节点指向后一个节点
            }
            //找到该节点后  判断该节点的尾指针指向下一个节点
            if (flag == 1){
                temp.next = temp.next.next;
            }else
                System.out.printf("要删除的 %d 节点不存在n", sno);
        }
    
    }
    
    //定义Node 每个Node都是一个对象
    class NodeList{
        public int sno;
        public String name;
        public int age;
        public String grade;
        public NodeList next;
    
        public NodeList(int sno, String name, int age, String grade) {
            this.sno = sno;
            this.name = name;
            this.age = age;
            this.grade = grade;
        }
    
        public void setSno(int sno) {
            this.sno = sno;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setGrade(String grade) {
            this.grade = grade;
        }
    
        public void setNext(NodeList next) {
            this.next = next;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "sno=" + sno +
                    ", name='" + name + ''' +
                    ", age=" + age +
                    ", grade='" + grade + ''' +
                    '}';
        }
    }
    

    单链表常见的几种类型的问题

    ​ 1.求单链表中有效节点的个数

    //统计有效的节点个数
        public int size(){
            //链表为空 则返回长度0
            if (head.next == null) return 0;
            //定义一个辅助来记录当前的节点
            NodeList temp = head.next;
            int count = 0;//统计节点个数
            while(temp != null){
                count++;
                temp = temp.next;
            }
            return count;
        }
    

    ​ 2.查找单 链表中的倒数第k个结点

    //查找单链表中倒数第k个节点
        //思路
        //先把链表全部遍历一遍得到链表的长度,从链表的第一个节点开始,查找length - k 就可以得到该节点的数据
        //例如 length=3 k=1  我们所需要循环2次就可以找到该节点
    
        /**
         *
         * @param head  链表的头节点
         * @param index 倒数第k个节点
         * @return 倒数第k个节点的链表内容
         */
        public NodeList findLastIndexNode(NodeList head, int index){
            //判断链表是否为空
            if(head.next == null) return null;
            NodeList temp = head.next;
            for (int i = 0; i < size() - index; i++) {
                temp = temp.next;
            }
            return temp;
        }
    

    ​ 3.单链表的反转

    //链表反转
        public void reversalNode(NodeList head){
            if (head.next == null || head.next.next == null){
                System.out.println("链表为空!");return ;
            }
            NodeList cur = head.next;//用cur来记录当前的node节点
            NodeList next = null;//用来记录cur的下一个节点
            NodeList reversalHead = new NodeList(0,"",0,"");//用来记录reversal节点
            while(cur != null){
                next = cur.next;
                cur.next = reversalHead.next;//将 cur 的下一个节点指向新的链表的最前端
                reversalHead.next = cur; //将 cur 连接到新的链表上
                cur = next;//让 cur 后移
            }
            //将 head.next 指向 reverseHead.next , 实现单链表的反转
            head.next = reversalHead.next;
        }
    

    ​ 这里可能有两行代码很难理解

    cur.next = reversalHead.next;//将 cur 的下一个节点指向新的链表的最前端
    reversalHead.next = cur; //将 cur 连接到新的链表上
    

    image-20220228221234490

    ​ 4.从尾到头打印单链表 [要求方式1:反向遍历。方式2: Stack栈]

    //从尾到头打印单链表 【百度,要求方式 1:反向遍历 。 方式 2:Stack 栈
        public void reversePrint(NodeList head){
            //判断链表是否为空
            if (head.next == null) return ;
            //创建要给一个栈,将各个节点压入栈
            Stack stack = new Stack();
            NodeList cur = head.next;
            while(cur != null){
                stack.push(cur);
                cur = cur.next;
            }
            while(stack.size()>0){
                System.out.println(stack.pop());
            }
        }
    

    ​ 5.合并两个有序的单链表,合并之后的链表依然有序

    public void mergeNode(NodeList head1, NodeList head2){
            //定义一个心链表
            NodeList newnode = new NodeList(0,"",0,"");
            //定义新链表的表头
            NodeList temp = newnode;
            //判断链表的个数如果链表为空返回另一链表内容
            if(head1.next == null) newnode = head2;
            if(head2.next == null) newnode = head1;
            NodeList cur1 = head1.next;
            NodeList cur2 = head2.next;
    
            //判断链表的sno大小 ,将链表的下一个节点指向所需要的指向的节点
            while(cur1 != null && cur2 != null){
                if(cur1.sno > cur2.sno){
                    newnode.next = cur2;
                    cur2 = cur2.next;
                }else{
                    newnode.next = cur1;
                    cur1 = cur1.next;
                }
                newnode = newnode.next;
            }
            //判断完成后,如果该cur1节点为空 将剩余的cur2链表的内容通过newnode指向cur2
            if (cur1 == null) newnode.next = cur2;
            if (cur2 == null) newnode.next = cur1;
            while(temp.next!=null){
                temp = temp.next;
                System.out.println(temp);
            }
        }
    

    双向链表(带头结点)

    管理单向链表的缺点分析

    • 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
    • 单向链表不能自我删除,需要靠辅助节点 ,而双向链表,则可以自我删除,所以前面我们单链表删除时节点总是找到 temp,temp 是待删除节点的前一个节点。
    • 分析了双向链表如何完成遍历,添加,修改和删除的思路。

    思路

    image-20220226194551337

    遍历

    和单链表一样,只是可以向前,也可以向后查找

    添加 (默认添加到双向链表的最后)

  • 先找到双向链表的最后这个节点
  • temp.next = node;
  • node.pre = temp;
  • 修改

    和单向链表一样。

    删除

    (1) 因为是双向链表,因此,我们可以实现自我删除某个节点

    (2) 直接找到要删除的这个节点,比如temp

    (3) temp.pre.next = temp.next

    (4) temp.next.pre = temp.pre;

    代码

    package com.cz.LinkedList;
    
    /**
     * @ProjectName: Data_structure
     * @Package: com.cz.LinkedList
     * @ClassName: DoubleLinkedListDemo
     * @Author: 张晟睿
     * @Date: 2022/2/26 18:33
     * @Version: 1.0
     */
    public class DoubleLinkedListDemo {
        public static void main(String[] args) {
            DoubleLinedList doubleLinedList = new DoubleLinedList();
            NodePoint nodePoint1 = new NodePoint(1,"孙悟空",19,"西游记");
            NodePoint nodePoint2 = new NodePoint(2,"唐僧",17,"西游记");
            NodePoint nodePoint3 = new NodePoint(3,"猪八戒",26,"西游记");
            NodePoint nodePoint4 = new NodePoint(4,"沙悟净",30,"西游记");
            NodePoint nodePoint5 = new NodePoint(4,"白骨精",32,"西游记");
            doubleLinedList.add(nodePoint1);
            doubleLinedList.add(nodePoint2);
            doubleLinedList.add(nodePoint3);
            doubleLinedList.add(nodePoint4);
    
            doubleLinedList.update(nodePoint5);
    
            doubleLinedList.delete(nodePoint5.sno);
            doubleLinedList.list();
        }
    }
    class DoubleLinedList{
        private NodePoint head = new NodePoint(0,"",0,"");
    
        // 遍历双向链表的方法
        public void list(){
            if (head.next == null) {
                System.out.println("链表为空");
                return ;
            }
            NodePoint temp = head.next;
            while(temp != null){
                System.out.println(temp);
                temp = temp.next;
    
            }
        }
        // 添加一个节点到双向链表的最后.
        public void add(NodePoint node){
            NodePoint temp = head;
            while(temp.next != null){
                temp = temp.next;
            }
            temp.next = node;
            node.pre = temp;
        }
        // 修改一个节点的内容, 可以看到双向链表的节点内容修改和单向链表一样
        public void update(NodePoint updatenode){
            boolean flag = false;
            NodePoint temp = head.next;
            while(true){
                if(temp == null) break;
                if (temp.sno == updatenode.sno){
                    flag = true;
                    break;
                }
                temp = temp.next;
            }
            if (flag == true){
                temp.name = updatenode.name;
                temp.age = updatenode.age;
                temp.grade = updatenode.grade;
            }else
                System.out.printf("要修改的 %d 节点不存在,无发修改n", updatenode.sno);
        }
        // 从双向链表中删除一个节点, // 说明
        // 1 对于双向链表,我们可以直接找到要删除的这个节点
        // 2 找到后,自我删除即可
        public void delete(int sno){
            if (head.next == null) {// 空链表
                System.out.println("链表为空,无法删除");
                return;
            }
            boolean flag = false;
            NodePoint temp = head.next;
            while (true){
                if (temp == null) return ;
                if (temp.sno == sno) {// 找到的待删除节点的前一个节点 temp
                    flag = true;
                    break;
                }
                temp = temp.next;
            }
            if (flag){
                temp.pre.next = temp.next;
                //判断是否为最后一个节点,如果最后一个节点不进行判断会出现nullException
                if (temp.next != null) {
                    temp.next.pre = temp.pre;
                }
            }else {
                System.out.printf("要删除的 %d 节点不存在n", sno);
            }
    
        }
    }
    //定义Node 每个Node都是一个对象
    class NodePoint{
        public int sno;
        public String name;
        public int age;
        public String grade;
        public NodePoint pre;
        public NodePoint next;
    
        public NodePoint(int sno, String name, int age, String grade) {
            this.sno = sno;
            this.name = name;
            this.age = age;
            this.grade = grade;
        }
    
        public void setSno(int sno) {
            this.sno = sno;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setGrade(String grade) {
            this.grade = grade;
        }
    
    
        @Override
        public String toString() {
            return "Student{" +
                    "sno=" + sno +
                    ", name='" + name + ''' +
                    ", age=" + age +
                    ", grade='" + grade + ''' +
                    '}';
        }
    }
    

    约瑟夫问题

    这里我们用单循环链表进行题解。

    问题

    Josephu 问题为:设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 k(11->5->3

    图解

    image-20220228222907467

    代码

    package com.cz.LinkedList;
    
    /**
     * @ProjectName: Data_structure
     * @Package: com.cz.LinkedList
     * @ClassName: Josephu
     * @Author: 张晟睿
     * @Date: 2022/2/27 18:27
     * @Version: 1.0
     */
    public class Josephu {
        public static void main(String[] args) {
            CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
            circleSingleLinkedList.addChild(5);
    
            circleSingleLinkedList.showChild();
    
            circleSingleLinkedList.outcircle(1,2,5);
        }
    }
    
    class CircleSingleLinkedList{
        //创建一个first节点 当前没有编号
        private Child first = null;
    
        public void addChild(int nums) {
            //定义一个临时变量存放头节点
            Child curChild = null;
            if (nums < 1 ){
                System.out.println("nums 的值不正确,请重新输入");
            }
            for (int i = 1; i  nums || countNum < 0 || nums < 0){
                System.out.println("输入错误,请输入合适的参数");
            }
            Child point = first;    //辅助指针  用来辅助first来进行指针的指向
            while(true){
                if (point.getNext() == first){
                    break;
                }
                point = point.getNext();
            }
            //将两个指针从startNo开始进行操作
            for (int i = 0; i < startNo - 1; i++) {
                first = first.getNext();
                point = point.getNext();
            }
            while(true){
                if (first == point){    //说明该环中 只有一个孩子
                    break;
                }
                for (int i = 0; i < countNum - 1; i++) {
                    point = point.getNext();
                    first = first.getNext();
                }
                System.out.printf("小孩%d 出圈n", first.getNo());
    
                first = first.getNext();//first 指向下一个孩子节点
                point.setNext(first);//将point的指针指向first的节点 此时first的孩子已经出圈
            }
            System.out.printf("最后留在圈中的小孩编号%d n", first.getNo());
        }
    }
    class Child {
        private int no;//该节点的编号
        private Child next;//指向下一个节点
    
        public Child(int no) {
            this.no = no;
        }
    
        public int getNo() {
            return no;
        }
    
        public void setNo(int no) {
            this.no = no;
        }
    
        public Child getNext() {
            return next;
        }
    
        public void setNext(Child next) {
            this.next = next;
        }
    
        @Override
        public String toString() {
            return "Child{" +
                    "no=" + no +
                    '}';
        }
    }
    

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论