单向链表 - 底层实现 - Java

链表是由一个个节点组成的。每个节点的内存空间在堆上申请的。

共八种:主讲不带头 单向 不循环(算法常用),辅讲 不带头 双向 不循环(链表底层采取的这种)。

一个节点有两个部分,数据域 和 指针域。

区分一下头节点 和 首节点的概念?本篇代码中的head是首节点。

① 头插和尾插只用考虑正常和null的情况(先写正常情况下的代码,再判断其是否包含null的情况,如果不包含就需要额外讨论)

② 任意位置插入:判断index是否合法(链表为空会发生什么) + 正常 + 第一个节点位置 + size位置

③ contains,直接遍历即可,while的条件cur != null已经考虑了head为null的情况

④ remove,删除第一次key出现的节点,正常 + key为第一个节点 (有它的原因是,while的循环条件是cur.next != null,如果key就是第一个节点,按正常的逻辑根本删除它) +  null

⑤ removeAllKey,遍历一次就能删除所有key,正常key为第一个节点 + null

⑥ size,直接遍历即可

⑦ clear,直接head=null

⑧ display,直接遍历即可

注意:直接遍历时不用判空是因为循环条件cur != nul已经判断了。

           正常 和 null情况是必须要判断了,其他情况都是由于正常情况的代码有缺陷的补充。

           涉及到某某位置的问题一定要判断该位置是否合法。

           给链表新增元素时,是不必考虑是否扩容问题的。

           将Node类型写成泛型时,注意在比较两个节点中的value值时,要用equals方法。

一、IList

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: tangyuxiu
 * Date: 2024-08-08
 * Time: 6:53
 */
public interface IList<T> {
    void addFirst(T data);

    void addLast(T data);

    //第一个数据节点为0号下标
    void addIndex(int index,T data);

    boolean contains(T key);

    void remove(T key);

    //一次遍历移除所有key值
    void removeAllKey(T key);

    int size();

    void clear();

    void display();
}

二、IndexIsLegalException

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: tangyuxiu
 * Date: 2024-08-08
 * Time: 7:22
 */
public class IndexIsLegalException extends Exception {
    public IndexIsLegalException() {
        super();
    }

    public IndexIsLegalException(String s) {
        super(s);
    }
}

三、MyLinkedList

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: tangyuxiu
 * Date: 2024-08-08
 * Time: 6:52
 */
public class MyLinkedList<T> implements IList<T> {

    static class ListNode<T> {
        T value;
        ListNode<T> next;//默认值为null

        public ListNode(T value) {
            this.value = value;
        }
    }

    private ListNode<T> head;//首节点

    @Override
    public void addFirst(T data) {
        //正常,包含了head==null
        ListNode<T> newNode = new ListNode<>(data);
        newNode.next = this.head;
        this.head = newNode;
    }

    @Override
    public void addLast(T data) {
        ListNode<T> newNode = new ListNode<>(data);

        //head==null
        if (this.head == null) {
            newNode.next = this.head;
            this.head = newNode;
            return;
        }

        //正常,未包含head==null
        //找到最后一个节点
        ListNode<T> cur = this.head;
        while (cur.next != null) {
            cur = cur.next;
        }
        cur.next = newNode;
    }

    private void indextIsLegal(int index) throws IndexIsLegalException {
        //依据一下逻辑,当head==null,index==0时是合法的
        //但当head==null,index>0时是不合法的
        if (index < 0 && index > this.size()) {
            throw new IndexIsLegalException("下标不合法!!!");
        }
    }

    //第一个数据节点为0号下标
    @Override
    public void addIndex(int index, T data) {
        //判断index下标是否合法
        try {
            this.indextIsLegal(index);
        } catch (IndexIsLegalException e) {
            e.printStackTrace();
        }

        ListNode<T> newNode = new ListNode<>(data);

        //index=0
        if (index == 0) {
            //不管head是否为空,调用addFirst方法都满足前插
            this.addFirst(data);
            return;
        }

        //尾插,下列代码已含括

        //正常:下列代码当index=0或1时,均只会在1位置前插入,因此当index=0时要额外讨论
        //找到index的前一个节点
        ListNode<T> pre = this.head;
        for (int i = 0; i < index - 1 ; i++) {
            pre = pre.next;
        }
        ListNode<T> find = pre.next;
        newNode.next = find;
        pre.next = newNode;
    }

    @Override
    public boolean contains(T key) {
        ListNode<T> cur = this.head;
        while (cur != null) {
            if (cur.value.equals(key)) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    @Override
    public void remove(T key) {
        //head==null
        if (this.head == null) {
            return;
        }

        //key为第一个节点
        if (this.head.value == key) {
            this.head = this.head.next;
            return;
        }

        //正常
        //找到key的前一个节点
        ListNode<T> pre = this.head;
        ListNode<T> find = pre.next;
        while (pre.next != null) {
            if (find.value.equals(key)) {
                pre.next = find.next;
                return;
            }
            pre = find;
            find = find.next;
        }
    }

    //一次遍历移除所有key值
    @Override
    public void removeAllKey(T key) {
        //head==null
        if (this.head == null) {
            return;
        }

        //正常,下列代码无法检查第一个节点的value值所以要额外讨论第一个节点
        //找到找到key的前一个节点
        ListNode<T> pre = this.head;
        ListNode<T> find = pre.next;
        while (pre.next != null) {
            if (find.value.equals(key)) {
                pre.next = find.next;
                //find = find.next;
            } else {
                pre = find;
                //find = find.next;
            }
            find = find.next;
        }

        //key为第一个节点
        if (this.head.value == key) {
            //为什么下行代码不写成pre = pre.next呢?
            //答:“正常”的代码中pre,find只是工具,真正的head一直仍然指向的是同一个节点
            //附图,间文章中
            this.head = this.head.next;
        }
    }

    @Override
    public int size() {
        int count = 0;//size计数器
        ListNode<T> cur = this.head;
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }

    @Override
    public void clear() {
        this.head = null;
    }

    @Override
    public void display() {
        ListNode<T> cur = this.head;
        while (cur != null) {
            System.out.print(cur.value + " ");
            cur = cur.next;
        }
        System.out.println();
    }
}

四、Test

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: tangyuxiu
 * Date: 2024-08-08
 * Time: 6:53
 */
public class Test {
    public static void main(String[] args) {
//        MyLinkedList<String> myLinkedList = new MyLinkedList<>();

        //测试头插
//        myLinkedList.addFirst("kk");
//        myLinkedList.addFirst("qq");
//        myLinkedList.addFirst("ww");
//        myLinkedList.display();//ww qq kk

        //测试尾插
//        myLinkedList.addLast("kk");//head==null
//        myLinkedList.addLast("ee");
//        myLinkedList.addLast("ff");
//        myLinkedList.display();//kk ee ff

        //测试任意位置插入
//        myLinkedList.addIndex(0,"kk");//head==null
//        myLinkedList.addIndex(1,"qq");
//        myLinkedList.addIndex(1,"ss");
//        myLinkedList.display();//kk ss qq

        //测试contains
//        System.out.println(myLinkedList.contains("qq"));//true
//        System.out.println(myLinkedList.contains("xx"));//false

        //测试删除第一次出现的key
//        myLinkedList.addLast("kk");
//        myLinkedList.addLast("qq");
//        myLinkedList.addLast("ii");
//        myLinkedList.addLast("kk");
//        myLinkedList.addLast("kk");
//
//        myLinkedList.remove("kk");
//        //myLinkedList.display();//qq ii kk kk
//        myLinkedList.display();//qq ii kk

        //测试删除所有的key
//        myLinkedList.removeAllKey("kk");
//        myLinkedList.display();//qq ii

        //测试size
//        System.out.println(myLinkedList.size());//5

        //测试clear
//        myLinkedList.clear();
//        System.out.println(myLinkedList.size());//0
    }
}

本篇已完结 ...... 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值