数据结构与算法之单向链表的设计与实现

前言

动态数组有个明显的缺点:可能会造成内存空间的大量浪费。能否设计一种数据结构以达到用到多少就申请多少内存?链表可以办到这一点。

一、基础概念

1.1 线性表的类型

线性表是一种逻辑结构,这种逻辑结构在计算机的表现形式(存储结构)主要有两种:
1.线性存储:用顺序存储结构的线性表也叫做顺序表,一般用数组实现。
2.链式存储:用链式结构存储的线性表也叫做链表,链表由一个个节点组成节点由数据域和指针域组成。

1.2 链表定义

链表是一种链式存储的线性表,所有元素的内存地址不一定是连续的,如下图所示:
在这里插入图片描述

1.3 ArrayList和LinkedList谁更占空间?

一般情况下,LinkedList的占用空间更大,因为每个节点要维护指向前后地址的两个节点,但也不是绝对如果刚好数据量超过ArrayList默认的临时值时,ArrayList占用的空间也是不小的,因为扩容的原因会浪费将近原来数组一半的容量不过因为ArrayList的数组变量是用transient关键字修饰的,如果集合本身需要做序列化操作的话,ArrayList这部分多余的空间不会被序列化。

1.4 链表的优缺点

  • 优点:
    1.元素的存储单元是任意的可以连续也可以不连续,插入删除效率高只需要改变引用即可。
    2.没有空间限制存储的元素只要内存够大可以无上限存储。
    3.动态分配内存空间,不用事先开辟较为灵活,空间利用率高。
  • 缺点:
    1.查找效率低,涉及到从链表的头遍历到查询数据元素的位置。

二、接口设计

2.1 接口类图

在这里插入图片描述

2.2 清空元素-clear

在这里插入图片描述
frist = null 就已经指明链表元素没有被引用,会被gc回收。

@Override
public void clear() {
    size = 0;
    first = null;
}

2.3 新增元素-add

在这里插入图片描述

@Override
public void add(int index, E element) {
    /*
     * 最好:O(1)
     * 最坏:O(n)
     * 平均:O(n)
     */
    rangeCheckForAdd(index);
    // 给空链表添加第一个元素的情况
    if(index == 0){
        first = new Node<>(element, first);
    }else{
        Node<E> prev = node(index - 1);
        prev.next = new Node<>(element, prev.next);
    }
    size++;
}

2.4 获取元素–get

@Override
public E get(int index) {
	return node(index).element;
}

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

2.5 设置元素-set

@Override
public E set(int index, E element) {
    /*
     * 最好:O(1)
     * 最坏:O(n)
     * 平均:O(n)
     */
    E old = node(index).element;
    node(index).element = element;
    return old;
}

2.6 删除元素–remove

@Override
public E remove(int index) {
    /*
     * 最好:O(1)
     * 最坏:O(n)
     * 平均:O(n)
     */
    rangeCheck(index);
    Node<E> node = first;
    if (index == 0) { // 删除第一个元素是特殊情况
        first = first.next;
    } else {
        Node<E> prev = node(index - 1); // 找到前一个元素
        node = prev.next; // 要删除的元素
        prev.next = node.next; // 删除元素
    }
    size--;
    return node.element;
}

2.7 复杂度分析

在这里插入图片描述

2.8 代码实现

公用List接口封装:

public interface List<E> {

    static final int ELEMENT_NOT_FOUND = -1;
    /**
     * 清除所有元素
     */
    void clear();

    /**
     * 元素的数量
     * @return
     */
    int size();

    /**
     * 是否为空
     * @return
     */
    boolean isEmpty();

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

    /**
     * 添加元素到尾部
     * @param element
     */
    void add(E element);

    /**
     * 获取index位置的元素
     * @param index
     * @return
     */
    E get(int index);

    /**
     * 设置index位置的元素
     * @param index
     * @param element
     * @return 原来的元素ֵ
     */
    E set(int index, E element);

    /**
     * 在index位置插入一个元素
     * @param index
     * @param element
     */
    void add(int index, E element);

    /**
     * 删除index位置的元素
     * @param index
     * @return
     */
    E remove(int index);

    /**
     * 查看元素的索引
     * @param element
     * @return
     */
    int indexOf(E element);
}

抽象类AbstractList:

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

    public int size;

    /**
     *获取集合中的数据元素的个数
     */
    @Override
    public int size(){
        return size;
    }

    /**
     *判断集合是否为空(无数据元素)
     */
    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     *获取集合中数据元素是否包含element参数
     */
    @Override
    public boolean contains(E element){
        return false;
    }

    /**
     *向集合中添加数据元素
     */
    @Override
    public void add(E element){
       /*
        * elements[size] = element;
        * size++;
        */
        add(size, element);
    }

    public void outOfBounds(int index){
        throw new IndexOutOfBoundsException("Index:" + index+ ",Size:" +size);
    }

    public void rangeCheck(int index){
        if (index < 0 || index >= size){
            outOfBounds(index);
        }
    }

    public void rangeCheckForAdd(int index){
        if (index < 0 || index > size){
            outOfBounds(index);
        }
    }

}

实现类LinkedList:

public class LinkedList<E> extends AbstractList<E>{

    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;
        }
    }

    @Override
    public void clear() {
        size = 0;
        first = null;
    }

    @Override
    public E get(int index) {
        return node(index).element;
    }
    /**
     * 根据索引找到节点
     */
    private Node<E> node(int index) {
        rangeCheck(index);
        Node<E> node = first;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

    @Override
    public E set(int index, E element) {
        /*
         * 最好:O(1)
         * 最坏:O(n)
         * 平均:O(n)
         */
        E old = node(index).element;
        node(index).element = element;
        return old;
    }

    @Override
    public void add(int index, E element) {
        /*
         * 最好:O(1)
         * 最坏:O(n)
         * 平均:O(n)
         */
        rangeCheckForAdd(index);
        // 给空链表添加第一个元素的情况
        if(index == 0){
            first = new Node<>(element, first);
        }else{
            Node<E> prev = node(index - 1);
            prev.next = new Node<>(element, prev.next);
        }
        size++;
    }

    @Override
    public E remove(int index) {
        /*
         * 最好:O(1)
         * 最坏:O(n)
         * 平均:O(n)
         */
        rangeCheck(index);
        Node<E> node = first;
        if (index == 0) { // 删除第一个元素是特殊情况
            first = first.next;
        } else {
            Node<E> prev = node(index - 1); // 找到前一个元素
            node = prev.next; // 要删除的元素
            prev.next = node.next; // 删除元素
        }
        size--;
        return node.element;
    }

    @Override
    public int indexOf(E element) {
        // 有个注意点, 如果传入元素为null, 则不能调用equals方法, 否则会空指针
        // 因此需要对元素是否为null做分别处理
        if (element == null) {
            Node<E> node = first;
            for (int i = 0; i < size; i++) {
                if (node.element == null) return i;
                node = node.next;
            }
        } else {
            Node<E> node = first;
            for (int i = 0; i < size; i++) {
                if (node.element.equals(element)) return i;
                node = node.next;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    @Override
    public String toString() {
        StringBuilder string = new StringBuilder();
        string.append("[size=").append(size).append(", ");
        Node<E> node = first;
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                string.append(", ");
            }
            string.append(node.element);
            node = node.next;
        }
        string.append("]");
        return string.toString();
    }

}

测试类LinkedListTest :

public class LinkedListTest {
    public static void main(String[] args) {
       List<Integer> list = new LinkedList<>();
       list.add(10);
       list.add(20);
       list.add(30);
       list.add(list.size(),40);
       list.remove(1);
        System.out.println(list);
    }
}

运行结果:
在这里插入图片描述

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值