【数据结构】线性表之单链表

目录

一、链表的概念

1、概念

2、分类

3、重点

二、单链表模拟实现

1、准备

2、头插法

3、尾插法

 4、指定下标插入

5、遍历

6、删除第一次出现的元素key

7、删除所有的key

8、双指针删除所有的key


一、链表的概念

1、概念

是一种物理存储结构上非连续的存储结构,但逻辑上是连续的一种数据结构

2、分类

单向或双向

带头或者不带头

循环或者非循环

3、重点

后续重点学习无头单向非循环链表也就是单链表,他结构简单,一般不单独使用存储数据而是常作为其他数据结构的子结构比如哈希桶、图的临近表。还有一个是双向非循环链表,Java集合类里的LinkedList底层就是这一数据结构

二、单链表模拟实现

1、准备

一个单链表有多个节点,而每个节点都有一个存放数据的地方,我们叫他数据域,还有一个存下一个节点地址的地方,我们叫他指针域

public class SingleLinkedList {
    static class Node{
        public int val;   //数据域
        public Node next; //指针域

        /**
         * 构造节点
         * @param val  节点的数据
         */
        public Node(int val) {
            this.val = val;
        }
    }
}

2、头插法

先创建节点,创建节点后,将节点的指针域指向原来链表的头节点

此时需要更新头节点的位置,让头节点指向新增的节点 

 

 

/**
     * 头插法
     * @param data  数据
     */
    public void addFirst(int data){
        Node node = new Node(data);  //创建节点

        node.next = head;            //将该节点的指针域指向头节点
        head = node;                 //再将头指针指向这个新的节点完成头插
    }

3、尾插法

尾插法分两种情况:1.链表有节点,此时只需找到最后一个节点,然后将最后一个节点的指针指向插入的节点即可

再找最后一个节点时要注意,再循环里让cur遍历时,循环结束的条件不能是cur != null此时当循环结束,cur不是指向最后一个节点而是null。要找到最后一个节点 循环结束的条件是cur.next != null

2.第二个情况则是,如果此时链表没有任何元素,插入的为第一个元素,则直接让头指针指向他即可

/**
     * 尾插法
     * @param data  尾插的数据
     */
    public void addLast(int data){
        Node node = new Node(data);   //创建节点
        
        if(head == null){             //如果此时没有任何节点,则直接将头节点指向他
            head = node;
            return;
        }
        
        Node cur = head;              //代替头节点遍历链表直到最后一个节点时停止

        while(cur.next != null){
            cur = cur.next;
        }
        
        cur.next = node;              //此时cur指向最后一个节点,此时只需将他的指针域指向新的节点即可
    }

 4、指定下标插入

指定的位置插入数据,比如在2下标插入数据,此时我们先得找到前一个。让前一个节点的next指向新增的节点,然后将新节点的next指向第二个节点,不过此处要注意如果我们先让2的前一个节点指向新的节点,在第二部将新节点的next指向2的时候会发现丢失了,所以此处可以先让新节点的next指向2位置的节点,在让前面的节点next指向新的节点,综上所述,分为以下主要步骤:找到前驱节点,让新增节点的next指向前驱节点的下一个节点,然后再将前驱节点的next 指向新增节点

 

 

 

 /**
     * 中间指定位置插入
     * @param index  指定插入下标
     * @param data   插入的数据
     */
    public void addIndex(int index,int data){
        //1.判断下标是否合法
        if(index < 0 || index > size()){
            throw new ArrayIndexOutOfBoundsException("非法下标");
        }

        //2.处理特殊情况
        if(index == 0){
            addFirst(data);                  //如果插头则调用头插
            return;
        }

        if(index == size()){
            addLast(data);                   //如果插尾则调用尾插
            return;
        }

        //3.找到前驱节点
        Node pre = searchPrevNodeToAdd(index);  //找到前一个节点

        //4.开始插入
        Node node = new Node(data);       //创建节点
        node.next = pre.next;             //让新增节点的next指向前驱节点的下一个
        pre.next = node;                  //再将前驱节点的next指向新增节点
    }

    /**
     * 新增时找前驱节点
     * @param index   待插入的下标
     * @return        前驱节点
     */
    private Node searchPrevNodeToAdd(int index) {
        Node cur = head;

        while(index-- != 1){
            cur = cur.next;
        }

        return cur;
    }

5、遍历

链表的遍历,定义一个指针代替头节点进行循环遍历 

6、删除第一次出现的元素key

要删除node这个节点时,我们需要先找到他的前一个节点,然后让他前一个节点指向他的后一个节点,所以现在我们先需要找到前一个节点,然后再删除。


    /**
     * 删除节点时找前驱节点
     * @param key  关键字
     * @return     前驱节点
     */
    private Node searchPrevNodeToRemove(int key) {
        Node cur = head;
        while(cur.next != null){         //要找的是前驱节点
            if(cur.next.val == key){
                return cur;
            }
            cur = cur.next;
        }

        return null;             //没找到返回空
    }

此处找前驱节点的代码里遍历链表退出循环的条件使用的是cur.next != null此处要注意,如果使用cur!=null,链表遍历到最后一个节点时cur!=null满足条件进入循环里的if cur.next.val由于是最后一个节点所以他的next是空的,此时空的再去访问val则会空指针异常 

 找到前驱节点后进行删除,要注意处理看头节点是不是符合删除的条件

/**
     * 删除第一次出现的指定节点
     * @param key
     */
    public void remove(int key){
        //1.是否为空
        if(head == null){
            return;
        }

        //2.判断头节点是不是
        if(head.val == key){
            head = head.next;
            return;
        }

        //3.找到前一个节点
        Node pre = searchPrevNodeToRemove(key);
        if(pre == null) return;

        //4.正常删除
        pre.next = pre.next.next;
    }

    /**
     * 删除节点时找前驱节点
     * @param key  关键字
     * @return     前驱节点
     */
    private Node searchPrevNodeToRemove(int key) {
        Node cur = head;
        while(cur.next != null){         //要找的是前驱节点
            if(cur.next.val == key){
                return cur;
            }
            cur = cur.next;
        }

        return null;             //没找到返回空
    }

7、删除所有的key

删除所有的key,我们可以定义一个指针指向头节点,让他去遍历链表,遍历的过程如果有满足条件的就删除

/**
     * 删除所有的key
     * @param key
     */
    public void removeAll(int key){
        //1.判断是否空
        if(head == null){
            return;
        }

        //2.遍历删除
        Node cur = head;
        while(cur.next != null){
            if(cur.next.val == key){
                cur.next = cur.next.next;
                continue;
            }
            cur = cur.next;
        }

        //3.处理头节点
        if(head.val == key){
            head = head.next;
        }

    }

上述代码要注意的是,处理头节点要放在遍历删除之后,如果头节点满足条件第二个节点也满足条件,此时删除了头进入循环时新的头节点也是key,但是循环里的逻辑是不能删除满足条件的头节点的,所以放在后面进行处理。而在上面只删除第一个key,把处理头节点放在循环前的原因是他只是删除第一个分先后顺序删除

8、双指针删除所有的key

定义两个指针,一个作为遍历指,一个作为遍历指针的前驱指针,让遍历指针遍历链表的同时进行判断他当前的节点是否满足删除条件,如果满足则前驱节点的next指向当前节点的next直接跳过当前节点删除,然后遍历指针指向下一个节点,如果不满足则让前驱节点指向当前节点,当前节点指向下一个节点

 /**
     * 双指针删除所有的key
     * @param key
     */
    public void removeAllByDoublePointer(int key){
        //1.判空
        if(head == null){
            return;
        }

        //2.双指针删除
        Node pre = head;
        Node cur = head.next;
        while(cur != null){
            if(cur.val == key){
                pre.next = cur.next;
            }else{
                pre = cur;
            }
            cur = cur.next;
        }

        //3.处理头节点
        if(head.val == key){
            head = head.next;
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

1886i

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

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

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

打赏作者

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

抵扣说明:

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

余额充值