小白也能学会的链表(java)——第一关青铜挑战

目录

1.单链表的概念

1.1链表的概念

 2.链表的相关概念

2.1节点和头节点

2.2虚拟节点

 3.创建链表

3.1 概念:首要要先理解JVM是怎么构建出链表的

4.链表的增删改查

4.1遍历链表

4.2链表插入

4.2.1在链表的表头插入

4.2.2在链表中间插入

4.2.3在单链表的结尾插入结点

 4.3链表的删除

4.3.1删除表头结点

4.3.2删除最后一个结点

4.3.3删除中间结点


1.单链表的概念

算法的基础是数据结构,任何数据结构的基础都是创建+增删改查,所有的链表算法题分解到最后,都是这几个操作,所以下面也从这五项开始学习链表。

1.1链表的概念

什么是链表?

答:单向链表就像一个铁链一样,元素之间相互连接,包含多个节点,每个节点有一个指向后继元素的next指针。表中最后一个元素的next指向null。如下图

注意:单链表中(核心的一个节点只能有一个后继,但是不代表一个节点只能有一个被指向。也就是好比法律规定一夫一妻,你只能和一个人结婚,不能同时跟两个人结婚,但是可以很多个人去爱你),就比如图二,图三就不符合

 2.链表的相关概念

2.1节点和头节点

在链表中,每个点都由指向下一个结点的地址组成的独立单元,称为一个结点,有时也称为节点,含义都是一样的。

对于单链表,如果知道了第一个元素,就可以通过遍历访问整个链表,因此第一个结点最重要,一般称为“头结点”

2.2虚拟节点

在做题以及在工程里经常会看到虚拟结点的概念,其实就是一个结点dummyNode,其next指针指向head,也就是dummyNode.next=head。

因此,如果我们在算法里使用了虚拟结点,则要注意如果要获得head结点,或者从方法(函数)里返回的时候,就应该使用dummyNode.next。

注意:dummyNode的val不会被使用,初始化为0或者-1等都是可以的,既然值不会使用,那虚拟结点有啥用?

答:为了方便我们在处理首部结点,否则我们需要在代码里单独处理首部结点的问题。

 3.创建链表

3.1 概念:首要要先理解JVM是怎么构建出链表的

我们知道JVM里有栈区和堆区,栈区主要存引用,也就是一个指向实际对象的地址,而堆区存的才是创建的对象。

public class Course{
    Teacher teacher;
    Student student;
}

这里的teacher和student就是指向堆的引用,比如我们再定义一个:

public class Course{
    int val;
    Course next;
}

这个时候 next就指向了下一个同为Course类型的对象了,例如:


​​​​​​​

这里通过栈中的引用(也就是地址)就可以找到val(1)。然后val(1)结点又存了指向cal(2)的地址,而val(3)又存了指向 val(4)的地址,所以就构造出来了一个链条访问结构。

根据面向对象的理论,在JAVA里规范的链表应该这么定义 :

public class ListNode1 {
    private int data;
    private ListNode1 next;
    public ListNode1(int data) {
        this.data = data;
    }
    public int getData() {
        return data;
    }
    public void setData() {
         this.data = data;
    }
    public ListNode1 getNext(){
        return next;
    }
    public void setNext(){
        this.next=next;
    }
}

但是在LeetCode中算法题经常这样的方式来创建链表:

 public class ListNode() {
        public int val;
        public ListNode next;

        ListNode(int x) {
            val = x;
            next = null;
        }
    }

    ListNode listNode = new ListNode(1);

 这里的val就是当前结点的值,next指向下一个结点。

因为两个变量都是public的,创建对象后能直接使用listnode.val和listnode.next来操作,虽然违背了面向对象的设计要求,但是上面的代码更为精简,所以在算法题目中应用广泛。

4.链表的增删改查

4.1遍历链表

对于单链表,不管进行什么操作,一定是从头开始逐个向后访问的,所以操作之后是否还能找到表头是非常重要的,“千万注意”->“不能只顾当前位置而将标记表头的指针弄丢了”。

代码如下

public static int getListLength(Node head) {
        int length = 0;
        Node node = head;
        while (node != null) {
            length++;
            node = node.next;
        }
        return length;
    }

4.2链表插入

单链表的插入,和数组插入是一样的,过程不复杂,但是很多坑 。

首先单链表插入要考虑三种情况:首部、中部、尾部

4.2.1在链表的表头插入

在链表的表头插入新的结点非常简单,容易出粗的是经常会忘记head需要重新指向表头,当我们创建一个新结点为newNode时,怎么连接到原来的链表上呢?

执行newNode.next=head即可。

但是之后我们还是要遍历新链表就要从newNode开始一路nex向下了,对吧?

但是我们的习惯是让head来表示,所以更改为head=newNode就好。

4.2.2在链表中间插入

step1:先遍历找到要插入的位置,之后将当前位置接入到前驱结点和后继结点之间。

注意:到这里我们发现,既不能获得前驱结点,也无法将结点接入进来。

step2:我们要在目标结点前的一个位置停下来,也就是使用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之间的连线就自动断开了,如下图所示:

4.2.3在单链表的结尾插入结点

表尾插入就比较容易,我们只需要将尾结点指向新结点就可以了

 综上,我们写出的链表插入的方法如下所示:

/**
     * 
     * @param head 链表头结点
     * @param nodeInsert 带插入结点
     * @param position 带插入位置,从1开始
     * @return 插入后得到的链表头结点
     */
    public static Node insertNode(Node head, Node nodeInsert, int position) {
        if (head == null) {
            return nodeInsert;
        }
        //已经存放的元素个数
        int size = getListlength(head);
        if (position < 1 || position > size + 1) {
            System.out.println("位置参数越界");
            return head;
        }
        if (position == 1) {
            nodeInsert.next = head;
            head = nodeInsert;
            return head;
        }
        Node pNode = head;
        int count = 1;
        while (count < position - 1) {//找要插入的前一个元素
            pNode = pNode.next;
            count++;
        }
        nodeInsert.next = pNode.next;
        pNode.next = nodeInsert;
        return head;
    }

 4.3链表的删除

删除同样分为:删除头部元素、中间元素、尾部元素

4.3.1删除表头结点

执行:head=head.next即可。将head向前移动一次之后,原来的结点不可达,就会被JVM回收掉。

4.3.2删除最后一个结点

同样也是要找到“要删除的结点的”前驱结点,这里同样在提前一个位置判断,例如下图中删除40 ,前驱结点是7。遍历的时候需要判断cur.next时候为40,如果是,则执行cur.next=null即可,原来的40结点不可达,就会被JVM回收,

4.3.3删除中间结点

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

完整代码实现:

/**
     * 
     * @param head 链表头结点
     * @param position 删除节点位置,取值从1开始
     * @return 删除后的链表头结点
     */
    public static Node deleteNode(Node head,int position){
        if (head==null){
            return null;
        }
        int size=getListLength(head);
        if (position<1||position>size){
            System.out.println("输入的参数有误");
            return head;
        }
        if (position==1){
            return head.next;
        }else {
            Node cur=head;
            int count=1;
            while (count<position-1){
                cur=cur.next;
                count++;
            }
            Node curNode=cur.next;
            cur.next=curNode.next;
            //上面两行可以直接简化成:cur.next=cur.next.next
        }
        return head;
    }

  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值