美文网首页
三、链表

三、链表

作者: 咸鱼Jay | 来源:发表于2022-01-09 18:42 被阅读0次

一、链表

  • 动态数组有个明显的缺点

    • 可能会造成内存空间的大量浪费
  • 能否用到多少就申请多少内存?

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

    链表

二、链表的设计

链表的设计
public class LinkedList<E> {

    private int size;
    private Node<E> first;
    
    private static class Node<E>{
        E element;
        Node<E> next;
        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }
}

三、链表的接口设计

链表的大部分接口和动态数组是一致的

int size(); // 元素的数量

boolean isEmpty(); // 是否为空

boolean contains(E element); // 是否包含某个元素

void add(E element); // 添加元素到最后面

E get(int index); // 返回index位置对应的元素

E set(int index, E element); // 设置index位置的元素

void add(int index, E element); // 往index位置添加元素

E remove(int index); // 删除index位置对应的元素

int indexOf(E element); // 查看元素的位置

void clear(); // 清除所有元素

那么链表和动态数组的很多接口是一样的,就可以设计一个接口List。

但是有一部分接口的实现是相同的(size、isEmpty、contains、add),有些接口的实现是不相同的,于是可以在抽象一个父类AbstractList来实现链表和动态数组相同接口实现。


List接口:

public interface List<E> {
    
    static final int ELEMENT_NOT_FOUND = -1;
    
    int size(); // 元素的数量

    boolean isEmpty(); // 是否为空

    boolean contains(E element); // 是否包含某个元素

    void add(E element); // 添加元素到最后面

    E get(int index); // 返回index位置对应的元素

    E set(int index, E element); // 设置index位置的元素

    void add(int index, E element); // 往index位置添加元素

    E remove(int index); // 删除index位置对应的元素

    int indexOf(E element); // 查看元素的位置

    void clear(); // 清除所有元素
    
}

AbstractList类实现:

public abstract class AbstractList<E> implements List<E>{

    protected int size;
    
    /**
     * 元素的数量
     * @return
     */
    @Override
    public int size() {
        return size;
    }

    /**
     * 是否为空
     * @return
     */
    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 是否包含某个元素
     * @param element
     * @return
     */
    @Override
    public boolean contains(E element) {
        return indexOf(element) != ELEMENT_NOT_FOUND;
    }

    /**
     * 添加元素到尾部
     * @param element
     */
    @Override
    public void add(E element) {
        add(size,element);
    }
    
    protected void outOfBounds(int index) {
        throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
    }
    
    protected void rangeCheck(int index) {
        if (index < 0 || index >= size) {
            outOfBounds(index);
        }
    }
    
    protected void rangeCheckForAdd(int index) {
        if (index < 0 || index > size) {
            outOfBounds(index);
        }
    }
}

四、清空元素 - clear()

清空元素
  • 清空元素只需要将first置为null就可以了,然后size置为0。
  • next是不需要置为null的,因为first = null后,first置为null之前的元素是没有GCRoot引用的,会被GC自动回收。
public void clear() {
    first = null;
    size = 0;
}

五、添加元素 - add(int index,E element)

findNode(int index)

如果想添加一个元素,得先找到要添加元素位置的前一个元素才可以完成,所以这里先实现一个findNode方法用于根据索引查找对应的元素

/**
* 获取index位置对应的节点对象
* @param index
* @return
*/
private Node<E> findNode(int index){
    rangeCheck(index);
    
    Node<E> node = first;
    for(int i=0;i < index;i++) {
        node = node.next;
    }
    return node;
}

add(int index,E element)

public void add(int index, E element) {
    rangeCheckForAdd(index);
    if(index == 0) {
        first = new Node<E>(element, first);
    }else {
        Node<E> prev = findNode(index - 1);
        prev.next = new Node<E>(element, prev.next);
    }
    size++;
}

在编写链表过程中,要注意边界测试,比如 index 为 0 、size – 0 、size 时

六、获取和设值 - get(ind index)、set(int index,E element)

上面findNode方法实现了,那么对应的get和set方法也可以实现了。

public E get(int index) {
    return findNode(index).element;
}

public E set(int index, E element) {
    Node<E> node = findNode(index);
    E old = node.element;
    node.element = element;
    return old;
}

七、删除元素 - remove(int index)

public E remove(int index) {
    rangeCheck(index);//如果链表中没有数据
    Node<E> node = first;
    if(index == 0) {
        first = first.next;
    }else {
        Node<E> prev = findNode(index - 1);
        node = prev.next;
        prev.next = node.next;
    }
        size--;
    return node.element;
}

八、查看元素的索引 - indeOf(E element)

/**
 * 查看元素的索引
 * @param element
 * @return
 */
@Override
public int indexOf(E element) {
    if (element == null) {  // 1
        for (int i = 0; i < size; i++) {
            Node<E> node = first;
            if (node.element == null) return I; 
            node = node.next;
        }
    } else {
        for (int i = 0; i < size; i++) {
            Node<E> node = first;
            if (element.equals(node.element)) return I;
            node = node.next;
        }
    }
    return ELEMENT_NOT_FOUND;
}

九、推荐一个神奇的网站

数据结构和算法动态可视化

数据结构和算法动态可视化

代码链接

相关文章

  • JavaScript数据结构与算法-链表练习

    链表的实现 一. 单向链表 二. 双向链表 三. 循环链表 练习 一. 实现advance(n)方法,使当前节点向...

  • 《数据结构与算法之美》04——链表

    链表:通过“指针”将一组零散的内存块串联起来使用。 数组vs链表 三种常见的链表:单链表、双向链表、循环链表。 单...

  • 链表

    链表和数组一样也支持查找,插入,删除操作。最常见的三种链表是:单链表,双链表和循环链表。 链表和数组的区别:数组需...

  • 数据结构-链表

    链表结构 链表结构五花八门,今天我重点给你介绍三种最常见的链表结构,它们分别是:单链表、双向链表和循环链表。我们首...

  • 数据结构与算法--链表

    数组和链接的内存分配情况: 下面介绍三种最常见的链表结构,它们分别是:单链表、双向链表和循环链表。 单链表 我们习...

  • 链表(三)

    我们知道顺序表存储数据时,需要一块连续的内存空间,插入删除时要进行数据搬迁,并不是那么灵活。 而现在就有这么一种数...

  • 三 链表

    1. 两个有序升序链表S1与S2,合并出S1与S2的并集有序升序链表S3 2、判断循环链表 3、设计一个复杂度为n...

  • 链表(三)

    单向链表 链表失去了顺序表随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大,但对存储空间的使用要相对...

  • 三、链表

    一、链表 动态数组有个明显的缺点可能会造成内存空间的大量浪费 能否用到多少就申请多少内存?链表可以办到这一点 链表...

  • redis 链表

    链表的实现方式有很多种,常见的主要有三个,单向链表、双向链表、循环链表。 1、单链表 结构:第一个部分保存或者显示...

网友评论

      本文标题:三、链表

      本文链接:https://www.haomeiwen.com/subject/vkcycrtx.html