手把手带你实现一个MySinglyLinkedList(单向链表)

作者简介:☕️大家好,我是intelligent_M,一个Java后端开发者!
当前专栏:intelligent_M—— 实现一个MySinglyLinkedList ,CSDN博客。

后续会更新Java相关技术栈。
创作不易 欢迎点赞评论!!!


自己实现一个MySinglyLinkedList

关键点一、同时持有头尾节点的引用
在力扣做题时,一般题目给我们传入的就是单链表的头指针。但是在实际开发中,用的都是双链表,而双链表一般会同时持有头尾节点的引用。
因为在软件开发中,在容器尾部添加元素是个非常高频的操作,双链表持有尾部节点的引用,就可以在 O(1) 的时间复杂度内完成尾部添加元素的操作。
对于单链表来说,持有尾部节点的引用也有优化效果。比如你要在单链表尾部添加元素,如果没有尾部节点的引用,你就需要遍历整个链表找到尾部节点,时间复杂度是 O(n);如果有尾部节点的引用,就可以在 O(1) 的时间复杂度内完成尾部添加元素的操作。

关键点二、虚拟头尾节点
「虚拟头尾节点」技巧,它的原理很简单,就是在创建双链表时就创建一个虚拟头节点和一个虚拟尾节点,无论双链表是否为空,这两个节点都存在。这样就不会出现空指针的问题,可以避免很多边界情况的处理。
举例来说,假设虚拟头尾节点分别是 dummyHead 和 dummyTail,那么一条空的双链表长这样:
dummyHead <-> dummyTail
如果你添加 1,2,3 几个元素,那么链表长这样:
dummyHead <-> 1 <-> 2 <-> 3 <-> dummyTail
你以前要把在头部插入元素、在尾部插入元素和在中间插入元素几种情况分开讨论,现在有了头尾虚拟节点,无论链表是否为空,都只需要考虑在中间插入元素的情况就可以了,这样代码会简洁很多。
当然,虚拟头结点会多占用一点内存空间,但是比起给你解决的麻烦,这点空间消耗是划算的。
对于单链表,虚拟头结点有一定的简化作用,但虚拟尾节点没有太大作用。

关键点三、内存泄露?
在前文 动态数组实现 中,我提到了删除元素时,要注意内存泄露的问题。那么在链表中,删除元素会不会也有内存泄露的问题呢?
尤其是这样的写法,你觉得有没有问题:
// 假设单链表头结点 head = 1 -> 2 -> 3 -> 4 -> 5
// 删除单链表头结点
head = head.next;
// 此时 head = 2 -> 3 -> 4 -> 5
细心的读者可能认为这样写会有内存泄露的问题,因为原来的那个头结点 1 的 next 指针没有断开,依然指向着节点 2。
但实际上这样写是 OK 的,因为 Java 的垃圾回收的判断机制是看这个对象是否被别人引用,而并不会 care 这个对象是否还引用着别人。
那个节点 1 的 next 指针确实还指向着节点 2,但是并没有别的指针引用节点 1 了,所以节点 1 最终会被垃圾回收器回收释放。所以说这个场景和数组中删除元素的场景是不一样的,你可以再仔细思考一下。
不过呢,删除节点时,最好还是把被删除节点的指针都置为 null,这是个好习惯,不会有什么代价,还可能避免一些潜在的问题。所以在下面的实现中,无论是否有必要,我都会把被删除节点上的指针置为 null。

  • 单链表代码实现
import java.util.NoSuchElementException;

/**
 * @author M
 * @version 1.0
 * 单链表
 */
public class MySinglyLinkedList<E> {

    private static class Node<E>{
        E val;
        Node<E> next;

        Node(E val){
            this.val = val;
            this.next = null;
        }
    }

    private Node<E> head;//虚拟的头节点
    //这里只添加了虚拟的头节点,没有加虚拟的尾节点(用处不大)
    private Node<E> tail; //实际的尾部节点引用
    private int size;//节点个数

    /**
     * 无参构造
     */
    public MySinglyLinkedList(){
        this.head = new Node<>(null);
        this.tail = head;
        this.size = 0;
    }

    /**
     * 从头部添加节点
     * @param e
     */
    public void addFirst(E e){
        Node<E> newNode = new Node<>(e);
        newNode.next = head.next;
        head.next = newNode;
        if(size == 0){//无元素
            //那么这个节点就是实际的尾节点
            tail = newNode;
        }
        size++;
    }

    /**
     * 从尾部添加
     * @param e
     */
    public void addLast(E e){
        Node<E> newNode = new Node<>(e);
        tail.next = newNode;
        tail = newNode;//更新尾节点引用
        size++;
    }

    /**
     * 指定位置添加元素
     * @param index
     * @param element
     */
    public void add(int index, E element){
        checkPositionIndex(index);

        if(index == size){
            addLast(element);
            return;
        }

        Node<E> prev = head;
        for(int  i = 0; i < index; i++){
            prev = prev.next;
        }
        Node<E> newNode = new Node<>(element);
        newNode.next = prev.next;
        prev.next = newNode;
        size++;
    }

    /**
     * 删除头节点
     * @return
     */
    public E removeFirst(){
        if(isEmpty()){
            throw new NoSuchElementException();
        }
        Node<E> first = head.next;
        head.next = first.next;
        if(size == 1){
            tail = head;
        }
        size--;
        return first.val;
    }

    /**
     * 删除尾节点并返回值
     * @return
     */
    public E removeLast(){
        if(isEmpty()){
            throw new NoSuchElementException();
        }
        Node<E> prev = head;
        while(prev.next != tail){
            prev  = prev.next;
        }
        E val = tail.val;
        prev.next = null;
        tail = prev;
        size--;
        return val;
    }

    /**
     * 删除节点并返回值
     * @param index
     * @return
     */
    public E remove(int index){
        checkElementIndex(index);

        Node<E> prev = head;
        for(int  i = 0; i < index; i++){
            prev = prev.next;
        }

        Node<E> nodeToRemove = prev.next;
        prev.next = nodeToRemove.next;
        if(index == size - 1) {//删除的是最后一个元素
            tail = prev;
        }
        size--;
        return nodeToRemove.val;
    }

    /**
     * 查询头节点
     * @return
     */
    public E getFirst(){
        if(isEmpty()){
            throw new NoSuchElementException();
        }
        return head.next.val;
    }

    /**
     * 获取尾节点
     * @return
     */
    public E getLast(){
        if(isEmpty()){
            throw new NoSuchElementException();
        }
        return getNode(size - 1).val;
    }

    /**
     * 根据索引获取值
     * @param index
     * @return
     */
    public E get(int index){
        checkElementIndex(index);
        Node<E> p = getNode(index);
        return p.val;
    }

    /**
     * 根据索引修改值 并返回旧值
     * @param index
     * @param element
     * @return
     */
    public E set(int index, E element){
        checkElementIndex(index);
        Node<E> p = getNode(index);

        E oldVal = p.val;
        p.val = element;

        return oldVal;
    }

    //***********************工具函数*******************************//

    /**
     * 返回元素个数
     * @return
     */
    public int size(){
        return size;
    }

    /**
     * 判断链表是否为空
     * @return
     */
    public boolean isEmpty(){
        return size == 0;
    }

    /**
     * 判断是否存在这个索引(这个位置是否有元素)
     * @param index
     * @return
     */
    private boolean isElementIndex(int index){
        return index >= 0 && index < size;
    }

    /**
     * 判断这个位置是否可以添加元素
     * @param index
     * @return
     */
    private boolean isPositionIndex(int index){
        return index >= 0 && index <= size;
    }

    /**
     * 检查index 索引位置是否可以存在元素
     * @param index
     */
    private void checkElementIndex(int index){
        if(!isElementIndex(index))
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
    }

    /**
     * 检查 index 索引位置是否可以添加元素
     * @param index
     */
    private void checkPositionIndex(int index){
        if(!isPositionIndex(index))
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
    }

    /**
     * 根据index 返回对应的值
     * @param index
     * @return
     */
    private Node<E> getNode(int index){
        //判断索引是否合法
        if(!isElementIndex(index)){
            return null;
        }
        Node<E> p = head.next;
        for(int i = 0; i < index; i++){
            p = p.next;
        }
        return p;
    }
}
  • 你可以把借助力扣第 707 题「设计链表」来验证自己的实现是否正确

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Intelligent_M

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

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

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

打赏作者

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

抵扣说明:

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

余额充值