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

链表

关于作者

  • 作者介绍

🍓 博客主页:作者主页

🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆

🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻

简介

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

image-20220226134528249

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

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

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

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

单链表(带头节点)

image-20220226134725293

实例

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

思路

添加

直接添加到链表的尾部

image-20220226140519063

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

思路的分析示意图:

image-20220226140847849

修改

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

  1. 根据 newHeroNode 的 no 来修改即可
  2. 说明我们在比较时,需要查找当前节点是否存在 如果存在才能进行修改
删除
  1. head 不能动,因此我们需要一个 temp 辅助节点找到待删除节点的前一个节点
  2. 说明我们在比较时,是 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<NodeList> 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

遍历

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

添加 (默认添加到双向链表的最后)
  1. 先找到双向链表的最后这个节点
  2. temp.next = node;
  3. 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(1<=k<=n)的人从 1 开始报数,数到 m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止,由此 产生一个出队编号的序列。

思路

首先我们需要构建一个大小为n的环,从第k个节点开始报数,当某人报数为m时,出圈,这样一直循环找出最终幸存者。

构建一个单向的环形链表思路

  1. 先创建第一个节点, 让 first 指向该节点,并形成环形

  2. 后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可.

遍历环形链表

  1. 先让一个辅助指针(变量) curChild,指向first节点

  2. 然后通过一个while循环遍历 该环形链表即可 curChild.next == first 结束

出圈规则

根据用户的输入,生成一个小孩出圈的顺序n = 5 , 即有5个人,k = 1, 从第一个人开始报数,m = 2

  1. 需求创建一个辅助指针(变量) helper , 事先应该指向环形链表的最后这个节点。补充: 小孩报数前,先让 first 和 helper 移动 k - 1次
  2. 当小孩报数时,让first 和 helper 指针同时 的移动 m - 1 次
  3. 这时就可以将first 指向的小孩节点 出圈 first = first .next helper.next = first 原来first 指向的节点就没有任何引用,就会被回收

出圈的顺序 2->4->1->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; i++) {
            Child child = new Child(i);
            if (i == 1 ){//如果只有一个节点
                first = child;
                first.setNext(first);//构成环
                curChild = first; //让 curChild 指向第一个小孩
            }else{
                curChild.setNext(child);//临时变量相当于之前的temp
                child.setNext(first);  //当最后一个节点指向头节点形成环状
                curChild = child; //curChild后移
            }
        }
    }

    // 遍历当前的环形链表
    public void showChild() {
        if (first == null){//判断链表是否为空
            System.out.println("当前链表中无孩子");
        }
        //定义一个临时变量存放头节点 头节点不能动
        Child curChild = first;
        while(true){
            System.out.printf("小孩的编号 %d \n", curChild.getNo());
            if (curChild.getNext() == first){
                break;
            }
            curChild = curChild.getNext();
        }
    }


    //找出最幸运的孩子
    //出圈操作

    /**
     *
     * @param startNo 从哪个编号开始
     * @param countNum  报数
     * @param nums  环中有多少个孩子
     */
    public void outcircle(int startNo, int countNum, int nums){

        if (startNo > 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 +
                '}';
    }
}
  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

java厂长

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

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

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

打赏作者

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

抵扣说明:

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

余额充值