手写 LinkedList(双向链表)

本文档展示了如何实现一个自定义的双向链表`MyLinkedList`,包括添加、插入、获取、替换和删除节点等操作。此外,还创建了一个`Student`测试实体类,并进行了实际操作演示,如添加学生对象、插入、获取、替换和移除节点,以及清空链表。
摘要由CSDN通过智能技术生成

1、MyList 接口

/**
 * 用于 LinkedList 继承
 *
 * @param <E>
 */
public interface MyList<E> {

    /**
     * 将指定节点追加到此列表的末尾
     *
     * @param element : 节点中存储的数据
     * @return : 返回是否插入成功 boolean
     */
    public boolean add(E element);

    /**
     * 在此列表中的指定位置插入指定节点。
     * 当前处于移动中的节点(如果有)和右方的任何后续节点(向其索引添加节点)移动。
     *
     * @param index   : 要添加的节点位置
     * @param element : 节点中存储的数据
     */
    public void add(int index, E element);

    /**
     * 获取指定索引位置的节点
     *
     * @param index : 要获取的节点位置
     * @return : 返回获取的节点数据 element
     */
    public E get(int index);

    /**
     * 替换指定索引位置的节点
     *
     * @param index   : 要替换的节点位置
     * @param element : 要替换的节点数据 element
     * @return : 返回旧的节点数据 element
     */
    public E set(int index, E element);

    /**
     * 移除指定索引位置的节点
     *
     * @param index : 要移除的节点位置
     * @return : 返回被移除的节点数据 element
     */
    public E remove(int index);

    /**
     * 获取 LinkedList 大小, 即 index + 1
     *
     * @return
     */
    public int size();

    /**
     * 清空链表
     */
    public void clear();

}

2、MyLinkedList 实现类

public class MyLinkedList<E> implements MyList<E> {

    /**
     * 0.01 链表头节点
     */
    private Node head;

    /**
     * 0.02 链表尾节点
     */
    private Node tail;

    /**
     * 0.03 MyLinkedList 的大小(它包含的Node数)
     */
    private int size;

    /**
     * 0.04 初始化双向链表结构
     */
    public MyLinkedList() {

    }

    /**
     * 1.00 将 element 作为最后一个节点进行连接
     *
     * @param element
     */
    void linkLast(E element) {
        //① 获取链表尾节点
        Node<E> t = tail;

        //② 新建一个 Node 节点, 并将 element 数据放入其中,
        // 该 Node 节点的上一个链接指向旧的 “tail 链表尾节点”,
        // 该 Node 节点的下一个链接指向 null 节点
        Node<E> newNode = new Node<>(t, element, null);

        //③ 将 ”新的 Node 节点“ 作为 “tail 链表尾节点”
        tail = newNode;

        //④ 判断 旧的 “tail 链表尾节点” 是否存在
        if (t == null) {
            //⑤ 不存在, 将 ”新的 Node 节点“ 作为 “head 链表头节点”
            head = newNode;
        } else {
            //⑥ 存在, 将 ”① 获取链表尾节点 t“ 的下一个链接指向 ”新的 Node 节点“
            t.next = newNode;
        }
        //⑦ MyLinkedList 的大小(它包含的Node数)+ 1
        size++;
    }

    /**
     * 2.01 判断 add() 方法的 index 是否合法
     *
     * @param index : 要处理的索引值
     */
    private void rangCheckForAdd(int index) {
        // 判断 index 是否合法
        if (index < 0 || index > size) {
            // 抛出 数组下标越界异常.
            throw new IndexOutOfBoundsException();
        }
    }

    /**
     * 2.02 判断 index 是否合法
     *
     * @param index : 要处理的索引值
     */
    private void rangCheck(int index) {
        // 判断 index 是否合法
        if (index < 0 || index >= size) {
            // 抛出 数组下标越界异常.
            throw new IndexOutOfBoundsException();
        }
    }


    /**
     * 2.03 在指定节点索引处返回(非空)节点, 如 : Node<E> succ.
     *
     * @param index : 要添加的节点位置
     * @return : 返回位置为 index 的 Node 节点
     */
    private Node<E> node(int index) {
        // assert isElementIndex(index);

        //① 要返回的 Node 节点
        Node<E> x;

        //② 假定传入的 index 小于 size/2
        if (index < (size >> 1)) {

            //③ 要返回的 Node 节点 = ”链表头“ 节点
            x = head;

            //④ 循环遍历, 直到找到位置为 index 的 Node 节点
            for (int i = 0; i < index; i++) {
                x = x.next;
            }
        } else {

            //⑤ 要返回的 Node 节点 = ”链表尾“ 节点
            x = tail;
            //⑥ 循环遍历, 直到找到位置为 index 的 Node 节点
            for (int i = size - 1; i > index; i--) {
                x = x.previous;
            }
        }
        //返回位置为 index 的 Node 节点
        return x;
    }

    /**
     * 2.03 在非空节点 succ 之前插入节点数据 element
     *
     * @param element : 要插入的节点数据  element
     * @param succ    : 在 Node<E> succ 前插入节点
     */
    private void linkBefore(E element, Node<E> succ) {
        //① 获取 ”非空节点 succ“ 链接指向的上一个节点
        // assert succ != null;
        Node<E> prev = succ.previous;

        //② 新建一个 Node 节点, 并将 element 数据放入其中,
        // 该 Node 节点的上一个链接指向 ”非空节点 succ “ 链接指向的上一个节点,
        // 该 Node 节点的下一个链接指向 succ 节点
        Node<E> newNode = new Node<>(prev, element, succ);

        //③ ”非空节点 succ“ 的上一个链接指向 ”新的 Node 节点“
        succ.previous = newNode;

        //④ 判断 ”①非空节点 succ “ 链接指向的上一个节点 是否存在
        if (prev == null) {
            //⑤ 不存在, 将 ”新的 Node 节点“ 作为 “head 链表头” 节点
            head = newNode;
        } else {
            //⑥ 存在, 将 “ ”①非空节点 succ“ 连接的上一个节点的下一个节点链接” 指向 ”新的 Node 节点“
            prev.next = newNode;
        }
        //⑦ MyLinkedList 的大小(它包含的Node数)+ 1
        size++;
    }

    /**
     * 2.04 取消链接非空节点 x
     *
     * @param x : 要移除的 Node<E> x 节点
     * @return : 被移除的 Node<E> x 节点的 element 数据
     */
    private E unlink(Node<E> x) {
        // assert x != null;
        //① 获取要移除的 Node<E> x 节点的 element 数据
        E element = x.element;
        //② 获取要移除的 Node<E> x 节点链接指向的 下一个节点
        Node<E> next = x.next;
        //③ 获取要移除的 Node<E> x 节点链接指向的 上一个节点
        Node<E> prev = x.previous;

        //④ 如果 Node<E> x 节点链接指向的上一个节点为空节点, 即: 为头节点
        if (prev == null) {
            //⑤ 将头节点设为 Node<E> x 节点链接指向 的下一个节点, 即: 当前节点的下一个节点
            head = next;
        } else { //⑥ 否则 Node<E> x 节点链接指向的上一个节点不是空节点
            //⑦ 将要移除的 Node<E> x 节点链接指向的 “上一个节点” 的下一个节点
            // 指向要移除的 Node<E> x 节点链接指向的 ”下一个节点“
            prev.next = next;
            //⑧ 将要移除的 Node<E> x 节点链接指向指向的 上一个节点链接 置为 null
            x.previous = null;
        }
        //④ 如果 Node<E> x 节点指向的下一个节点为空节点, 即: 为尾节点
        if (next == null) {
            //⑤ 将尾节点设为 Node<E> x 节点链接指向 的上一个节点, 即: 当前节点的上一个节点
            tail = prev;
        } else {  //⑥ 否则 Node<E> x 节点链接指向的下一个节点不是空节点
            //⑦ 将要移除的 Node<E> x 节点链接指向的 “下一个节点” 的上一个节点
            // 指向要移除的 Node<E> x 节点链接指向的 ”上一个节点“
            next.previous = prev;
            //⑧ 将要移除的 Node<E> x 节点链接指向的 下一个节点链接 置为 null
            x.next = null;
        }

        //⑨ 将将要移除的 Node<E> x 节点的 element 置为 null
        x.element = null;

        //⑩ MyLinkedList 的大小(它包含的Node数)-1
        size--;

        // 返回被移除的 Node<E> x 节点的 element
        return element;
    }

    /**
     * 1.10 将指定节点追加到此列表的末尾
     *
     * @param element : 数据
     * @return
     */
    @Override
    public boolean add(E element) {
        //将 element 作为最后一个节点进行连接
        linkLast(element);
        return true;
    }

    /**
     * 2.10 在此列表中的指定位置插入指定节点。
     * 将当前位于该位置的节点(如果有)和右下的任何后续节点(向其索引添加一个节点)移位。
     *
     * @param index   : 要添加的节点位置
     * @param element : 节点存储的 element 数据
     */
    @Override
    public void add(int index, E element) {

        //① 判断 index 是否合法
        rangCheckForAdd(index);

        //② 如果 index 等于链表长度
        if (index == size) {

            //③ 直接追加到此列表的末尾
            linkLast(element);
        } else {

            //⑤ 在此列表中的指定位置插入
            linkBefore(element, node(index));
        }
    }

    /**
     * 2.11 获取指定索引位置的节点存储的 element 数据
     *
     * @param index : 要获取的节点位置
     * @return : 返回 Node 节点中存储的 element 数据
     */
    @Override
    public E get(int index) {
        //① 判断 index 是否合法
        rangCheck(index);

        //② 返回 Node 节点中存储的 element 数据
        return node(index).element;
    }

    /**
     * 2.12 替换指定索引位置的节点
     *
     * @param index   : 要替换的节点位置
     * @param element : 要替换的 节点数据
     * @return : 返回 被替换的 Node 节点中的 element 数据
     */
    @Override
    public E set(int index, E element) {
        //① 判断 index 是否合法
        rangCheck(index);

        Node<E> x = node(index);

        //② 获取指定索引位置的 Node 节点中的 element 数据
        E oldValue = x.element;

        //③ 用 ”传入的 element“ 替换对应索引中的 Node 节点中的 element 数据
        x.element = element;

        //⑤ 返回被替换的 Node 节点中的 element 数据
        return oldValue;
    }

    /**
     * 2.13 移除指定索引位置的节点
     *
     * @param index : 要移除的节点位置
     * @return : 返回 被移除的 Node 节点中的 element 数据
     */
    @Override
    public E remove(int index) {
        //① 判断 index 是否合法
        rangCheck(index);

        //② 移除指定节点, 并返回被移除节点的 element 数据
        return unlink(node(index));
    }

    /**
     * 2.14 MyLinkedList 的大小(它包含的 Node 数)
     *
     * @return : 返回包含的 Node 数
     */
    @Override
    public int size() {
        return size;
    }

    /**
     * 2.15 清空 链表
     */
    @Override
    public void clear() {
        // 清除节点之间的所有链接是"不必要的",但:
        // 如果丢弃的节点居住在多代,则帮助生成 GC
        // 一定要释放内存,即使有一个可访问的迭代器
        //① 让 x = head 链表头, 只要 x 不为 null 就一直遍历
        for (Node<E> x = head; x != null; ) {
            //② 取出当前节点链接指向的 下一个节点
            Node<E> next = x.next;
            //③ 将当前节点的 element 数据置为空
            x.element = null;
            //④ 将 当前节点与下一节点的链接 置为空
            x.next = null;
            //⑤ 将 当前节点与上一节点的链接 置为空
            x.previous = null;
            //⑤ 将当前节点替换为 下一节点
            x = next;
        }
        //⑥ 将 头节点和尾节点 都置为 null
        head = tail = null;
        //⑦ 将 MyLinkedList 长度置为 0
        size = 0;
    }

    /**
     * 0.00 链表就是一系列存储数据元素的单元通过指针串接起来形成的.
     * <p>
     * 双向链表中的每个节点除了要保存它的下一个节点对象的引用以外,
     * 还会保存一个它前一个节点对象的引用,这样就可以实现双向查找数据.
     *
     * @param <E>
     */
    private static class Node<E> {

        /**
         * 0.00 用于数据元素的存储
         */
        private E element;

        /**
         * 0.00 上一个节点对象的链接
         */
        private Node<E> previous;

        /**
         * 0.0 下一个节点对象的链接
         */
        private Node<E> next;

        Node(Node<E> previous, E element, Node<E> next) {
            this.previous = previous;
            this.element = element;
            this.next = next;
        }

    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer("Size = " + size + ", Data = ").append("[");
        // 拼接第一个元素
        sb.append(this.head.element);

        Node node = this.head.next;
        // 获取当前节点链接指向的下一个节点
        for (int i = 0; i < size - 1; i++) {
            sb.append(", " + node.element);
            node = node.next;
        }
        sb.append("]");

        return sb.toString();
    }
}

3、Student 测试实体类

public class Student {

    private int id;

    private String name;

    private String address;

    public Student(int id, String name, String address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getAddress() {
        return address;
    }

    @Override
    protected void finalize() {
        System.out.println("This desctory!");
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", Address='" + address + '\'' +
                '}';
    }
}

4、测试类

public class Test {

    public static void main(String[] args) {

        MyLinkedList<Student> list = new MyLinkedList<>();
        list.add(new Student(1, "梨花1", "龙门客栈1"));
        list.add(new Student(2, "梨花2", "龙门客栈2"));
        list.add(new Student(3, "梨花3", "龙门客栈3"));
        list.add(new Student(4, "梨花4", "龙门客栈4"));
        System.out.println("添加元素" + list);

        list.add(2, new Student(10, "梨花10", "龙门客栈10"));
        System.out.println("插入元素" + list);

        System.out.println("获取指定索引位置的元素" + list.get(2));

        System.out.println("被替换的元素" + list.set(2, new Student(1000, "梨花1000", "龙门客栈1000")));
        System.out.println("替换完成的LinkedList" + list);

        System.out.println("被移除的元素" + list.remove(2));
        System.out.println("移除元素完成的LinkedList" + list);

        list.clear();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值