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

链表(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 +
    '}';
    }
    }