算法通关第一村-链表青铜挑战(Java)

本文介绍了链表的基本概念,包括单链表的定义、节点和头节点、虚拟结点的作用,展示了如何创建链表、初始化、遍历链表并计算节点个数。此外,还详细讲解了链表的插入和删除操作,包括在不同位置插入和删除节点的方法。
摘要由CSDN通过智能技术生成

算法通关第一村-链表青铜挑战(Java)


1 单链表的概念

1.1 链表的概念

  • 首先看一下什么是链表?单向链表就像一个铁链一样,元素之间相互连接,包含多个结点,每个结点有一个指向后继元素的next指针。表中最后一个元素的next指向null。如下图:
  • 在这里插入图片描述
  • 思考一下:你是否了解链表的含义?思考下面两个图,是否都满足单链表的要求为什么?
  • 在这里插入图片描述
  • 第二个图:
  • 在这里插入图片描述
  • 解析:
  • 上面第一个图是满足单链表要求的,因为我们说链表要求环环相扣,核心是一个结点只能有一个后继,但不代表一个结点只能有一个被指向。第一个图中,c1被a2和b3同时指向,这是没关系的。这就好比法律倡导一夫一妻,你只能爱一个人,但是可以都多个人爱你。
    第二图就不满足要求了,因为c1有两个后继a5和b4。
    另外在做题的时候要注意比较的是值还是结点,有时可能两个结点的值相等,但并不是同一个结点,例如下图中,有两个结点的值都是1,但并不是同一个结点。
  • 在这里插入图片描述

##2. 链表的相关概念
+节点和头节点
在链表中,每个点都由值和指向下一个结点的地址组成的独立的单元,称为一个结点,有时也称为节点,含义都是一样的。
对于单链表,如果知道了第一个元素,就可以通过遍历访问整个链表,因此第一个结点最重要,一般称为头结点。

  • 虚拟结点
    在做题以及在工程里经常会看到虚拟结点的概念,其实就是一个结点dummyNode,其next指针指向head,也就是dummyNode.next=head。
    因此,如果我们在算法里使用了虚拟结点,则要注意如果要获得head结点,或者从方法(函数)里返回的时候,则应使用dummyNode.next。
    另外注意,dummyNode的val不会被使用,初始化为0或者-1等都是可以的。既然值不会使用,那虚拟结点有啥用呢?简单来说,就是为了方便我们处理首部结点,否则我们需要在代码里单独处理首部结点的问题。在链表反转里,我们会看到该方式可以大大降低解题难度。

3创建链表

public class LinkListNode {
    public int val;//数据域
    public LinkListNode next;//指针域
    public LinkListNode(int val){
        this.val = val;
    }
}


链表初始化

初始链表应考虑到,头结点与后面结点的处理方法不同,可以分开处理。

public LinkListNode head = null;
    public LinkListNode initLinkList(int[] arr) {//链表初始化
        LinkListNode cur = null;
        for(int i = 0; i< arr.length; i++){
            //给链表中每个节点的数据域赋值
            LinkListNode newLinkListNode = new LinkListNode(arr[i]);
            //将链表通过指针域连起来
            //第一个节点
            if(i ==0){
                head = newLinkListNode;//让节点head和cur指向第一个节点
                cur = newLinkListNode;
            }else {//后面的节点
                cur.next = newLinkListNode;//循环,cur的指针域依次指向后面节点
                cur = newLinkListNode;//cur指向下一个节点
            }
        }
        return head;
    }


链表的遍历

  • 对于单链表,不管进行什么操作,一定是从头开始逐个向后访问,所以操作之后是否还能找到表头非常重要。一定要注意"狗熊掰棒子"问题,也就是只顾当前位置而将标记表头的指针丢掉了。
  • 在这里插入图片描述
public void traverseLinkList(){//链表的遍历
    LinkListNode cur = head;
    while(cur != null){//链表节点不为空时
        System.out.print(cur.val + "-> ");
        cur = cur.next;
    }
    System.out.println("null");
}

链表节点个数

public int getLinkListLength() {//链表的的节点个数
        int length = 0;
        LinkListNode cur = head;
        while(cur != null){//链表节点不为空时
            length++;
            cur = cur.next;
        }
        return length;
    }

链表插入

链表插入,写代码前要有清晰的构思,插入到头结点之前,插入到链表中间,插入到尾节点后面,下面三个图可以作为三种情况的参考,要考虑到插入位置‘location’值的范围。后面的链表删除也同样,当两者的‘location’值是不同的。

  • 单链表的插入,和数组的插入一样,过程不复杂,但是在编码时会发现处处是坑。单链表的插入操作需要要考虑三种情况:首部、中部和尾部。
    (1) 在链表的表头插入
    链表表头插入新结点非常简单,容易出错的是经常会忘了head需要重新指向表头。我们创建一个新结点newNode,怎么连接到原来的链表上呢?执行newNode.next=head即可。之后我们要遍历新链表就要从newNode开始一路next向下了是吧,但是我们还是习惯让head来表示,所以让head=newNode就行了,如下图:
  • 在这里插入图片描述
  • (2)在链表中间插入
    在中间位置插入,我们必须先遍历找到要插入的位置,然后将当前位置接入到前驱结点和后继结点之间,但是到了该位置之后我们却不能获得前驱结点了,也就无法将结点接入进来了。这就好比一边过河一边拆桥,结果自己也回不去了。
    为此,我们要在目标结点的前一个位置停下来,也就是使用cur.next的值而不是cur的值来判断,这是链表最常用的策略。
    例如下图中,如果要在7的前面插入,当cur.next=node(7)了就应该停下来,此时cur.val=15。然后需要给newNode前后接两根线,此时只能先让new.next=node(15).next(图中虚线),然后node(15).next=new,而且顺序还不能错。
    想一下为什么不能颠倒顺序?
    由于每个节点都只有一个next,因此执行了node(15).next=new之后,结点15和7之间的连线就自动断开了,如下图所示:
  • 在这里插入图片描述
  • (3)在单链表的结尾插入结点
    表尾插入就比较容易了,我们只要将尾结点指向新结点就行了。
  • 在这里插入图片描述

2023-12-01 224814

2023-12-01 225109

//链表节点的插入(插到头节点之前,插到链表中间,插到尾结点之后)
    //location->插入到第几个节点,最小值为1,element->要插入节点的数据域值
    public LinkListNode insertLinkList(int location, int element){
        //要插入的新节点
        LinkListNode newNode = new LinkListNode(element);
        LinkListNode cur = head;
        int i = 1;
        if(head == null){//可以认为要插入的节点就是头结点,也可以抛出不能插入的异常
            return newNode;
        }
        //链表的节点个数
        int curLinkLength = getLinkListLength();
        //插入位置不合理
        if(location > (curLinkLength + 1) || location < 1 ){
            System.out.println("链表插入位置错误! ");
            return head;
        }
        if(location == 1){//插入到头结点之前
            newNode.next = head;
            newNode.val = element;
            head = newNode;
            return head;
        }
        //插入到链表中间或尾结点之后
        while(i < (location - 1)){
            i++;
            cur = cur.next;//找到要插入节点位置的前一个节点
        }
        newNode.val = element;
        newNode.next = cur.next;
        cur.next = newNode;

        return head;
    }

链表删除

  • 删除同样分为在删除头部元素,删除中间元素和删除尾部元素。
    (1)删除表头结点
    删除表头元素还是比较简单的,一般只要执行head=head.next就行了。如下图,将head向前移动一次之后,原来的结点不可达,会被JVM回收掉。

  • 在这里插入图片描述

  • (2)删除最后一个结点
    删除的过程不算复杂,也是找到要删除的结点的前驱结点,这里同样要在提前一个位置判断,例如下图中删除40,其前驱结点为7。遍历的时候需要判断cur.next是否为40,如果是,则只要执行cur.next=nul即可,此时结点40变得不可达,最终会被JVM回收掉。

  • 在这里插入图片描述

  • (3)删除中间结点
    删除中间结点时,也会要用cur.next来比较,找到位置后,将cur.next指针的值更新为
    cur.next.next就可以解决,如下图所示:

  • 在这里插入图片描述

2023-12-01 230951

2023-12-01 231142

//链表节点的删除
    //location->删除第几个节点,最小值为1
    public LinkListNode deleteLinkList(int location){
        LinkListNode cur = head;
        if(head == null){
            return null;
        }
        //链表的节点个数
        int curLinkLength = getLinkListLength();
        //删除位置不合理
        if(location > curLinkLength || location < 1) {
            System.out.println("链表删除位置不合理");
            return head;
        }
        if(location == 1){//删除头结点
            head = head.next;
            return head;
        }
        int count = 1;
        //删除链表中间节点或尾节点
        while(count < (location - 1)){
            count++;
            cur = cur.next;
        }
        cur.next = cur.next.next;
        return head;
    }

测试

int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        MyLinkList myLinkList = new MyLinkList();
        myLinkList.head = myLinkList.initLinkList(arr);
        myLinkList.traverseLinkList();
        System.out.println("链表长度为:-> " + myLinkList.getLinkListLength());
        myLinkList.insertLinkList(1, 996);
        myLinkList.insertLinkList(12, 55);
        myLinkList.insertLinkList(8, 121);
        myLinkList.traverseLinkList();
        System.out.println("插入后链表长度为:-> " + myLinkList.getLinkListLength());
        myLinkList.deleteLinkList(13);
        myLinkList.deleteLinkList(1);
        myLinkList.deleteLinkList(4);
        myLinkList.traverseLinkList();
        System.out.println("删除后链表长度为:-> " + myLinkList.getLinkListLength());
  • 15
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

种一棵树leng

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

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

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

打赏作者

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

抵扣说明:

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

余额充值