看完这篇,你也可以手撕链表

一、链表概念及其结构

1.1 链表和数组的区别

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
在这里插入图片描述
数组是两个元素在物理结构上紧紧相连
在这里插入图片描述

1.2 单向链表定义

链表属性定义

在这里插入图片描述
head:头节点
size:当前链表中包含的节点个数
在这里插入图片描述
每—列火车由若干个车厢组成,每个车厢就是一个Node对象,由多个Node对象组成的大实体就是链表对象。

/**
* 基于整形的单链表
* */
public class singleLinkList {
   
    //单链表头节点
    private Node head;
    //当前节点个数=有效数值的个数
    private int size;
    }

节点属性定义

val:当前节点保存的数据
next:保存下一个节点的地址
在这里插入图片描述

/**
 * 单链表具体的每个节点
 */
class Node {
   
    int val;//每个结点的值
    Node next;//当前节点指向下一个节点的地址
    }

二、单链表的基本操作(无虚拟头节点)

在这里插入图片描述

2.1 头插法

具体步骤:
1.创建新节点,并给新节点赋值
2.第一种情况:如果链表中没有头节点,新节点就是头节点,head=newNode
3.第二种情况:链表中存在头节点,需要先newNode.next=head,然后再进行head=newNode,这两个操作的顺序很重要,不可以颠倒
在这里插入图片描述

代码如下(示例):

/**
     * 向当前链表插入新节点 - 头插法
     * @param val
     */
    public void addFirst(int val){
   
        Node newNode=new Node(val);
        if(head!=null){
   
            newNode.next=head;
        }
        head=newNode;
        size++;
    }

无论单链表最后是否为空,新节点都是单链表头插法后新的头节点head=node
在这里插入图片描述

2.2 遍历链表

从当前头节点开始,依次取出每个节点值,然后通过next引用走到下一个节点,直到走到链表的末尾(next = null)

head头节点是否可以遍历链表

不可以。直接使用head引用,遍历一次链表之后,链表就丢了。所以要使用一个临时变量,暂存head的值。只要拿到链表头节点就一定可以遍历整个链表。
在这里插入图片描述

代码如下(示例):

/**
     * 单链表的打印方法
     * @return
     */
    public String toString(){
   
        String str="";
        Node x=head;
        while (x!=null){
   
            str+=x.val;
            str+="->";
            x=x.next;
        }
        str+="Null";
        return str;
    }

在这里插入图片描述
也可以使用for循环遍历:
在这里插入图片描述

2.3 在index索引处插入新元素val

核心操作:找待插入位置的前驱节点

找到前驱节点后,操作1和操作2的顺序如何?能否颠倒?
操作顺序为先2后1,不可以颠倒顺序,如果先1后2,新创建的节点又是自己连接自己。

在这里插入图片描述
单链表的插入和删除,最核心的就是在找前驱结点,因为链表只能从前向后操作。但是链表中的头节点很特殊,只有头节点没有前驱节点。

易错点:

1.前驱节点prev要走的步数:找到待插入位置的前驱就是让prev引用从头结点开始先后走index -1步恰好就走到前驱结点(带入具体实例即可求出)

2.index索引指的是,新节点插入后的索引值就是index

/**
     * 在单链表的任意索引位置插入新元素val
     * @param index
     */
    public void add(int index,int val){
   
        //1.判断插入索引index的合法性
        //==size就相当于尾插法
        if(index<0||index>size){
   
            System.err.println("index is illegal");
        }
        //2.找前驱节点
        //头节点需要特殊处理,头节点不存在前驱节点,直接头插法
        if(index==0){
   
            addFirst(val);
        }else{
   
            //3.索引位置合法,且不是头节点,找到相应的前驱节点
            //前驱节点从头开始遍历
            Node prev=head;
            //创建待插入新节点
            Node newNode=new Node(val);
            //index - 1需要通过画图求解
            for (int i = 0; i <index-1 ; i++) {
   
                prev=prev.next;
            }
            //此时prev已经走到待插入index的前驱节点
            newNode.next=prev.next;
            prev.next=newNode;
            size++;
        }
    }

在这里插入图片描述
在这里插入图片描述

额外知识点:面向对象的优点

链表和数组的add方法名使用规则完全相同,只需更改类就可以实现链表到数组的转换
在这里插入图片描述

2.4 尾插法

在这里插入图片描述
所有的尾插法都是在index为size的位置插入元素

//尾插法
    public void addLast(int val){
   
    //直接复用在index位置插入元素的办法
        addIndex(size,val);
    }

2.5 查询

在这里插入图片描述

查询第一个值为val的索引

/**
     * 查询第一个值为val元素的索引
     * @return
     */
    public int getByVal(int val){
   
        int index=0;
        //遍历链表的方法
        for (Node x=head;x!=null;x=x.next){
   
            if(x.val==val){
   
                return index;
            }
            index++;
        }
        //for循环之后没有返回相应的index,说明不存在val
        //返回一个非法索引下标
        return -1;
    }

判断链表中枢否存在值为val元素

/**
     * 判断链表中是否包含值为val元素
     * @param val
     * @return
     */
    public boolean contains(int val){
   
        int index=getByVal(val);
        return index!=-1;
    }

不借助方法也可以判断:直接for循环遍历链表,如果node.val==val就return true否则return false

查询索引为index的节点val

任何牵扯到index的方法都需要判断index的合法性
在这里插入图片描述

/**
     * index合法性判断
     * @param index
     * @return
     */
    private boolean rangeCheck(int index) {
   
        if(index<0||index>size){
   
            return false;
        }
        return true;
    }

根据索引index的值,查询index位置元素的val值

/**
     * 根据索引index返回相应的val
     * @param index
     * @return
     */
    public int get(int index){
   
        if(rangeCheck(index)){
   
            Node x=head;
            for (int i = 0; i < index; i++) {
   
                x=x.next;
            }
            return x.val;
        }
        return -1;
    }

2.6 修改

在这里插入图片描述

/**
     * 修改index位置val为newVal,并放回修改前的val值
     * @param index
     * @param newVal
     * @return
     */
    public int set(int index,int newVal){
   
        if(rangeCheck(index)){
   
            Node x=head;
            for (int i = 0; i < index; i++) {
   
                x=x.next;
            }
            int oldVal=x.val;
            x.val=newVal;
            return oldVal;
        }
        System.err.println("index is illegal!set error");
        return -1;
    }

2.7 删除(重点)

在这里插入图片描述

删除核心操作:找前驱

操作1和2不能颠倒顺序
在这里插入图片描述
头节点需要特殊处理:头节点没有前驱
一个对象只有没有被任何引用指向,才能被JVM回收
在这里插入图片描述

删除索引为index的val

/**
     * 删除index位置节点,并返回该节点val
     * @param index
     * @return
     */
    public int remove(int index){
   
        if(rangeCheck(index)){
   
            //特殊处理:头删
            if(index==0){
   
                Node x=head;
                head=head.next;
                x.next=null;
                return x.val;
            }else{
   
                //删除中间节点
                //找前驱
                Node prev=head;
                for (int i = 0; i < index-1 ; i++) {
   
                    prev=prev.next;
                }
                //此时prev位于待删除节点的前驱
                Node node=prev.next;
                prev.next=node.next;
                node.next=null;
                size--;
                return node.val;
            }
        }
        System.err.println("index is illegal!remove error");
        return -1;
    }

node=null和node.next=null

node=null不等于node.next=null
在这里插入图片描述
node==null,node中包含的next引用还存在,就是图中的连线
在这里插入图片描述

删除第一次出现val的元素(考虑边界)

头节点需要特殊处理:
在这里插入图片描述
前驱节点一定不是待删除节点,在保证prev!=null的前提下,还需要保证prev.next!=null,因为我们需要判断的是prev.next.val是否为待删除节点,如果不保证prev.next!=null就可能出现空指针异常的情况!
在这里插入图片描述

/**
     * 删除链表中第一次出现val的节点
     * @param val
     */
    public void removeValOnce(int val){
   
        //判空
        if(head==null){
   
            System.err.println("head is null!can not removeVal!");
            return;
        }
        //头节点为待删除节点
        if(head.val==val){
   
            Node node=head;
            head=head.next;
            //断内部引用
            node.next=null;
            //JVM回收整个引用
            node=null;
            size--;
        }else{
   
            //头节点不是待删除节点的情况
            Node prev=head;
            //前驱节点不是待删除节点
            //需要判断prev节点的后继val值,所以要保证prev.next不为null
            while (prev.next!=null){
   
                if(prev.next.val==val){
   
                    Node node=prev.next;
                    prev.next=node.next;
                    node.next<
  • 27
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值