手写 LinkedList(双向链表)

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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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();
    }
}
### Java双向链表的实现与用法 #### 1. 双向链表的概念 双向链表是一种线性数据结构,其中每个节点不仅包含指向下一个节点的指针 `next`,还包含指向前一个节点的指针 `prev`。这种设计使得可以在两个方向上遍历链表。 #### 2. 节点类的设计 为了实现双向链表,首先需要定义一个表示节点的类。该类通常包含三个成员变量:前驱节点指针 `prev`、后继节点指针 `next` 和存储的数据值 `val`。 以下是节点类的一个典型实现: ```java class ListNode { public ListNode prev; public ListNode next; public int val; public ListNode(int num) { this.val = num; } public ListNode() {} } ``` 此部分描述了如何通过自定义类来构建节点对象[^2]。 #### 3. 链表类的实现 接下来,可以基于上述节点类创建一个完整的双向链表类。这个类应提供常用的操作方法,例如添加元素、删除元素以及清空列表等操作。 下面是一个简单的双向链表类实现: ```java public class DoublyLinkedList { private ListNode head; // 头节点 private ListNode tail; // 尾节点 private int size; // 当前链表大小 // 添加到头部 public void addFirst(int value) { ListNode newNode = new ListNode(value); if (head == null) { // 如果链表为空,则初始化头尾节点 head = tail = newNode; } else { newNode.next = head; head.prev = newNode; head = newNode; } size++; } // 添加到尾部 public void addLast(int value) { ListNode newNode = new ListNode(value); if (tail == null) { // 如果链表为空,则初始化头尾节点 head = tail = newNode; } else { tail.next = newNode; newNode.prev = tail; tail = newNode; } size++; } // 删除指定值的第一个匹配项 public boolean remove(int value) { ListNode current = head; while (current != null) { if (current.val == value) { if (current.prev != null) { current.prev.next = current.next; } else { head = current.next; // 更新头节点 } if (current.next != null) { current.next.prev = current.prev; } else { tail = current.prev; // 更新尾节点 } size--; return true; } current = current.next; } return false; } // 清空链表 public void clear() { head = tail = null; size = 0; } // 打印链表内容 public void display() { ListNode current = head; while (current != null) { System.out.print(current.val + " "); current = current.next; } System.out.println(); } } ``` 这段代码展示了如何利用自定义的 `ListNode` 类来管理双向链表的核心逻辑[^3]。 #### 4. 测试双向链表的功能 可以通过编写测试程序验证以上功能是否正常工作。以下是一段用于演示的主函数代码: ```java public class TestDoublyLinkedList { public static void main(String[] args) { DoublyLinkedList list = new DoublyLinkedList(); // 向链表中添加元素 list.addFirst(1); list.addFirst(1); list.addFirst(3); list.addLast(1); list.addLast(1); list.addLast(6); // 显示当前链表状态 list.display(); // 移除第一个等于1的节点并再次显示 list.remove(1); System.out.println("==========="); list.display(); // 清空整个链表 list.clear(); System.out.println("============"); list.display(); } } ``` 这里提供了具体的使用场景和调用方式。 #### 总结 Java双向链表可以通过手动定义节点类和链表类的方式轻松实现。它支持多种基本操作,如插入、删除和查询等功能。此外,在实际开发过程中也可以直接使用标准库中的 `java.util.LinkedList` 来代替手写版本[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值