数据结构之链表

730 阅读2分钟

链表(LinkedList)

概念

  • 链表是一种链式存储的线性表,所有存储的元素的内存地址不一定是连续的

image.png

链表接口设计

LinkedList,从字面上来看和ArrayList有一点相同,所以LinkedList和ArrayList有一些相同的方法。可能方法名跟ArrayList相同,但是里面的代码实现完全不一样,也有可能代码实现一模一样。所以先展示LinkedList的接口方法,后续逐个实现对应的方法

  • 代码
public class LinkedList<E> {

    private int size;
    private Node<E> firstNode;

    // 元素的数量
    int size();
    // 是否为空
    boolean isEmpty();
    // 是否包含某个元素
    boolean contains(E element);
    // 添加元素到最后面
    void add(E element);
    // 返回index位置对应的元素
    E get(int index);
    // 设置index位置的元素
    E set(int index, E element);
    // 往index位置添加元素
    void add(int index, E element);
    // 删除index位置对应的元素 
    E remove(int index);
    // 查看元素的位置
    int indexOf(E element);
    // 清除所有元素
    void clear();

    // 私有类, 链表中的节点
    private class Node<E> {
        E element;
        Node<E> next;
        // 构造方法
        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }
}

单向链表的实现

构造方法

  • 代码
private Node firstNode;

private static class Node<E> {
    E element;
    Node<E> next;

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

清空元素(Clear)

  • 图解

image.png

  • 分析

链表的长度是4个,size=4,清空整个链表,只需要first的地址指向为null,同时size置为0即可

  • 代码
public void clear() {
    firstNode = null;
    size = 0;
}

获取index位置对应节点对象(node)

  • 分析

因为链表的空间元素地址不是连续的,所以寻址速度相对于数组来讲是很慢的,如果需要通过index位置找到对应的节点对象,需要进行整个链表的遍历。

  • 代码
private Node<E> node(int index) {
    rangeCheck(index);
    Node<E> node = firstNode;
    for (int i = 0; i < index; i++) {
        node = node.next;
    }
    return node;
}

获取某个元素的位置(Get)

  • 分析

通过node方法可以获取到对应位置的节点对象,自然就得到了对应位置的元素。

  • 代码
public E get(int index) {
    Node<E> node = node(index);
    return node.element;
}

设置某个位置的元素(Set)

  • 分析

通过node方法可以获取到对应位置的节点对象,设置一个变量获得原位置的元素,然后再把元素进行替换。

  • 代码
public E set(int index, E element) {
    Node<E> node = node(index);
    E oldElement = node.element;
    node.element = element;
    return oldElement;
}

添加链表元素(add)

  • 图解

image.png

  • 分析

常见情况:元素添加在中间或者尾部,获取到上个链表所在的链表节点(prev),然后创建新的链表节点,将next指定prev.next,然后prev.next重新指向新链表节点。

特殊情况:元素添加在第一位,创建新的链表节点,然后将firstNode赋给新节点的next,在将新节点重新赋给firstNode。

最后链表长度加一。

  • 代码
public void add(int index, E element) {
    rangeCheckForAdd(index);
    if (index == 0) {
        Node<E> newNode = new Node<>(element, firstNode);
        firstNode = newNode;
    } else {
        Node<E> prev = node(index - 1);
        Node<E> newNode = new Node<>(element, prev.next);
        prev.next = newNode;
    }
    size++;
}

删除指定元素(remove)

  • 图解

image.png

  • 分析

常见情况,删除中间位置或者尾部,直接通过node方法获得上一个节点的信息,然后将上一个节点的地址指向下一个节点的next。然后链表长度减一。

  • 代码
public E remove(int index) {
    rangeCheck(index);
    Node<E> node = node(index);
    if (index == 0) {
        firstNode = firstNode.next;
    } else {
        Node<E> prev = node(index - 1);
        prev.next = prev.next.next;

    }
    size--;

    return node.element;
}

双向链表