手写真正的动态数据结构-链表

本文深入探讨链表作为动态数据结构的重要性,讲解链表的基本概念、实现方式及操作方法,包括添加、查询、删除等核心功能,并通过代码示例详细说明。

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

链表

线型数据结构

为什么链表很重要

  • 链表是真正的动态数据结构

  • 链表是最简单的动态数据结构

  • 可以更深入的理解引用(指针)

  • 可以更深入的理解递归

  • 可以辅助组成其他的数据结构

什么是链表(LinkedList)

  • 数据存储在"节点"(Node)中

class Node {
    
    /**
     * 所存储的真正的数据
     */
    E e;
    
    /**
     * 指向当前节点的下一个节点
     */
    Node next;
}

  • 优点:真正的动态数据结构,不需要处理固定容量的问题

  • 缺点:丧失了随机访问的能力

数组和链表的对比

  • 数组最好用于索引有语意的情况,如:scores[2]

  • 数组最大的优点就是:支持快速查询

  • 链表不适合于索引有语意的情况

  • 链表最大的优点:动态

链表的基本实现

package com.ldc.datastructures.linkedlist;

/**
 * @author lengdongcheng
 */
public class LinkedList<E> {

    /**
     * 内部类
     */
    private class Node{
        /**
         * 存放的真实的数据
         */
        public E e;

        /**
         * 指向该节点的下一个节点
         */
        public Node next;

        /**
         * 构造函数
         * @param e
         * @param next
         */
        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        /**
         * 只传一个e的构造函数
         * @param e
         */
        public Node(E e) {
            this(e, null);
        }

        /**
         * 默认构造函数
         */
        public Node() {
            this(null, null);
        }

        /**
         * 重写toString方法
         * @return
         */
        @Override
        public String toString() {
            return e.toString();
        }
    }
}

链表头添加元素

图示


  • 将新增加的节点挂接到原来的节点上,让新增的Node节点的next指向将要挂在的节点的头部

  • 维护head,将head头部指向新增的节点

  • 最终效果

代码演示

package com.ldc.datastructures.linkedlist;

/**
 * @author lengdongcheng
 */
public class LinkedList<E> {

    /**
     * 内部类
     */
    private class Node {
        /**
         * 存放的真实的数据
         */
        public E e;

        /**
         * 指向该节点的下一个节点
         */
        public Node next;

        /**
         * 构造函数
         *
         * @param e
         * @param next
         */
        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        /**
         * 只传一个e的构造函数
         *
         * @param e
         */
        public Node(E e) {
            this(e, null);
        }

        /**
         * 默认构造函数
         */
        public Node() {
            this(null, null);
        }

        /**
         * 重写toString方法
         *
         * @return
         */
        @Override
        public String toString() {
            return e.toString();
        }
    }

    /**
     * 头部元素
     */
    private Node head;

    /**
     * 链表中元素的个数
     */
    private int size;

    /**
     * 默认的构造函数
     */
    public LinkedList() {
        //头部元素为空
        head = null;
        //元素的个数为0
        size = 0;
    }

    /**
     * 获取链表中元素的个数
     *
     * @return
     */
    public int getSize() {
        return size;
    }

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

    /**
     * 在链表的头部添加元素
     */
    public void addFirst(E e) {
/*        //将元素e包装成一个Node对象
        Node node = new Node(e);
        //将Node的next指向将要挂载的Node对象
        node.next = head;
        //维护一下head的指向,指向新添加的Node节点
        head = node;*/

        //上面三行代码可以用下面一行代码来表示
        head = new Node(e, head);

        //维护一下size
        size++;
    }
}

在链表中间添加元素

图示

  • 需求

  • 将node节点的next指向prev的下一个节点:node.next = prev.next

  • 将prev节点的next指向node节点:prev.next = node

  • 最后效果

  • 需要注意下顺序

代码实现

/**
     * 在链表的index(从0开始)位置添加元素
     * 在链表的中间添加元素不是一个常用的操作,这里只是练习用
     * @param index
     * @param e
     */
    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add failed,index < 0 || index > size.");
        }
        //新添加的元素添在链表的头部,则调用addFirst就好了(添加到0的位置需要特殊处理)
        if (index == 0) {
            addFirst(e);
        } else {
            //刚开始将prev初始化为head的位置
            Node prev = head;
            //一直遍历,刚开始指向的是head的这个位置,然后一直将head.next赋值给prev
            for (int i = 0; i < index - 1; i++) {
                prev = prev.next;
            }
            //将新添加的元素包装成Node对象
/*            Node node = new Node(e);
            //将node的下一个节点指向prev的下一个节点
            node.next = prev.next;
            //将prev的下一个节点指向node
            prev.next = node;*/

            //同样,可以将上面三行代码合成一行代码
            //new Node(e, prev.next); ----->将node这个节点于后面的节点挂接在一起
            prev.next = new Node(e, prev.next);

            size++;
        }
    }
    /**
     * 在链表的末尾位置添加元素
     * @param e
     */
    public void addLast(E e) {
        add(size, e);
    }

为链表设置虚拟头节点

  • 代码实现

    /**
     * 在链表的index(从0开始)位置添加元素
     * 在链表的中间添加元素不是一个常用的操作,这里只是练习用
     * @param index
     * @param e
     */
    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add failed,index < 0 || index > size.");
        }
        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            prev = prev.next;
        }
        //将虚拟头节点的next指向新节点与虚拟头节点后面节点挂载后的节点
        prev.next = new Node(e, prev.next);
        size++;
    }

    /**
     * 在链表的头部添加元素
     */
    public void addFirst(E e) {
        add(0, e);
    }

    /**
     * 在链表的末尾位置添加元素
     * @param e
     */
    public void addLast(E e) {
        add(size, e);
    }

查询以及更新链表中的元素

    /**
     * 根据索引获取链表中的元素(根据索引获取元素不常用,只是练习用)
     * @param index
     * @return
     */
    public E get(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("get failed,index < 0 || index > size.");
        }

        //虚拟节点后面的节点
        Node cur = dummyHead.next;
        for (int i = 0; i < index; i++) {
            //获取index位置的Node节点
            cur = cur.next;
        }
        //获取index位置的Node节点的元素
        return cur.e;
    }

    /**
     * 获取第一个节点的元素
     * @return
     */
    public E getFirst(){
        return get(0);
    }

    /**
     * 获取最后一个节点的元素
     * @return
     */
    public E getLast(){
        return get(size-1);
    }

    /**
     * 修改链表中的第index位置的元素
     * @param index
     * @param e
     */
    public void set(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("set failed,index < 0 || index > size.");
        }

        Node cur = dummyHead.next;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        cur.e = e;
    }

    /**
     * 查看链表是否包含元素e
     * @param e
     * @return
     */
    public boolean contains(E e) {
        Node cur = dummyHead.next;
        //如果cur为空的话,那么则表示整的链表都已经遍历完了一遍
        while (cur != null) {
            if (cur.e.equals(e)) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        Node cur = dummyHead.next;
        while (cur != null) {
            sb.append(cur + "->");
            cur = cur.next;
        }
        sb.append("NULL");
        return sb.toString();
    }

测试案例

package com.ldc.datastructures.linkedlist;

/**
 * @author lengdongcheng
 */
public class Main {
    public static void main(String[] args) {
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < 5; i++) {
            linkedList.addFirst(i);
            System.out.println(linkedList);
        }

        linkedList.add(2, 666);
        System.out.println(linkedList);
    }
}

测试结果

0->NULL
1->0->NULL
2->1->0->NULL
3->2->1->0->NULL
4->3->2->1->0->NULL
4->3->666->2->1->0->NULL

从链表中删除元素

图示

  • 需求

  • 实现

代码实现

    /**
     * 删除指定索引位置的元素
     * @param index
     * @return
     */
    public E remove(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("remove failed,index < 0 || index > size.");
        }
        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            //找到待删除节点之前的节点
            prev = prev.next;
        }
        Node retNode = prev.next;
        prev.next = retNode.next;
        retNode = null;
        size--;
        return retNode.e;
    }

    /**
     * 删除链表中的第一个元素
     * @return
     */
    public E removeFirst() {
        return remove(0);
    }

    /**
     * 删除链表中的最后一个元素
     * @return
     */
    public E removeLast() {
        return remove(0);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值