【数据结构】链表

一、何为链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

三、手写LinkedList

参考JDK中的LinkedList,写法基本一致,仅用于自己学习。

java 复制代码
public class LinkedList<E> implements List<E> {

    transient int size = 0;

    transient Node<E> first;

    transient Node<E> last;

    @Override
    public boolean add(E e) {
        linkLast(e); // 默认尾插
        return false;
    }

    /**
     * 头插法
     * @param e
     */
    private void linkFirst(E e) {
        Node<E> f = first;
        Node<E> newNode = new Node<>(e, null, f);
        first = newNode;
        if (f == null) {
            last = newNode;
        } else {
            f.prev = newNode;
        }
        size++;
    }

    /**
     * 尾插法
     * @param e
     */
    private void linkLast(E e) {
        Node<E> l = last;
        Node<E> newNode = new Node<>(e, l, null);
        last = newNode;
        if (l == null) {
            first = newNode;
        } else {
            l.next = newNode;
        }
        size++;
    }

    /**
     * 插链操作
     * @param e
     */
    private void unLink(Node<E> e) {
        Node<E> prev = e.prev;
        Node<E> next = e.next;
        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            e.prev = null;
        }
        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            e.next = null;
        }
        e.item = null;
        size--;
    }

    @Override
    public boolean addFirst(E e) {
        linkFirst(e);
        return true;
    }

    @Override
    public boolean addLast(E e) {
        linkLast(e);
        return true;
    }

    @Override
    public void remove(int index) {
        checkElementIndex(index);
        unLink(node(index));
    }

    private E unLinkFirst(Node<E> f) {
        E item = f.item;
        Node<E> next = f.next;
        f.item = null;
        f.next = null;
        first = next;
        if (next == null) {
            last = null;
        } else {
            next.prev = null;
        }
        size--;
        return item;
    }

    private E unLinkLast(Node<E> l) {
        E item = l.item;
        Node<E> prev = l.prev;
        l.item = null;
        l.prev = null;
        last = prev;
        if (prev == null) {
            first = null;
        } else {
            prev.next = null;
        }
        size--;
        return item;
    }

    @Override
    public E remove() { // 默认头删
        return removeFirst();
    }

    @Override
    public E removeFirst() {
        Node<E> f = first;
        if (f == null) {
            return null;
        }
        return unLinkFirst(f);
    }

    private Node<E> node(int index) {
        if (index < size >> 1) {
            Node<E> x = first;
            for(int i = 0; i < index; i++) {
                x = x.next;
            }
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--) {
                x = x.prev;
            }
            return x;
        }
    }

    private void checkElementIndex(int index) {
        if (!isElementIndex(index)) {
            throw new RuntimeException(outOfBoundsMsg(index));
        }
    }

    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

    @Override
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    
    private static class Node<E> {
        E item;
        Node<E> prev;
        Node<E> next;
        public Node(E item, Node<E> prev, Node<E> next) {
            this.item = item;
            this.prev = prev;
            this.next = next;
        }
    }
}

四、常见问题

  • JDK中LinkedList是双向链表,add方法默认是尾插法,remove方法默认是头删,是实现LRU算法的雏形
  • 相比于ArrayList,LinkedList更适用于在首尾部进行插入删除和查询操作。ArrayList的耗时操作主要体现在对元素的拷贝和位移,而LinkedList的耗时操作主要体现在遍历定位和创建节点,因此在不同数据规模下耗时不同,不能简单的认为LinkedList一定比ArrayList插入快。
相关推荐
小熳芋4 小时前
验证二叉搜索树- python-递归&上下界约束
数据结构
不穿格子的程序员8 小时前
从零开始写算法——链表篇2:从“回文”到“环形”——链表双指针技巧的深度解析
数据结构·算法·链表·回文链表·环形链表
诺....8 小时前
C语言不确定循环会影响输入输出缓冲区的刷新
c语言·数据结构·算法
长安er10 小时前
LeetCode876/141/142/143 快慢指针应用:链表中间 / 环形 / 重排问题
数据结构·算法·leetcode·链表·双指针·环形链表
workflower10 小时前
PostgreSQL 数据库的典型操作
数据结构·数据库·oracle·数据库开发·时序数据库
仰泳的熊猫10 小时前
1140 Look-and-say Sequence
数据结构·c++·算法·pat考试
EXtreme3511 小时前
栈与队列的“跨界”对话:如何用双队列完美模拟栈的LIFO特性?
c语言·数据结构·leetcode·双队列模拟栈·算法思维
松涛和鸣11 小时前
29、Linux进程核心概念与编程实战:fork/getpid全解析
linux·运维·服务器·网络·数据结构·哈希算法
hweiyu0011 小时前
数据结构:有向图
数据结构