恋上数据结构与算法:普通单向链表(三)

文章目录

(一)链表(Linked List)的简介
(二)链表(Linked List)的设计
(三)动态数组(Dynamic Array)的优化
(四)链表(Linked List)的实现:clear
(五)链表(Linked List)的实现:add
(六)链表(Linked List)的实现:get&set
(七)链表(Linked List)的实现:remove
(八)链表(Linked List)的实现:indexOf
(九)链表(Linked List)的代码汇总&测试
(十)补充
(十一)虚拟头结点

(一)链表(Linked List)的简介

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

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

(二)链表(Linked List)的设计

在这里插入图片描述
我们直接把方法都抽取到List父类是没用的,因为ArrayListLinkedList的实现有所差别(有相同,有不同)

正确的做法是让List成为接口,把方法的声明都放在List接口,由AbstractList去实现这个接口,对于ArrayListLinkedList的实现都相同的方法可以直接抽取到AbstractList中,然后让ArrayListLinkedList都继承AbstractList,并且实现抽象方法

List接口代码如下:
在这里插入图片描述
问题:为什么static final int ELEMENT_NOT_FOUND = -1;要写在List接口中
在这里插入图片描述
当我们使用多态的写法创建ArrayList时,执行list.indexOf(20)时,如果查询不到,相当于list.indexOf(20) == List.ELEMENT_NOT_FOUND,所以要写在List接口
其实也可以写在AbstractList抽象类中,但是从设计的角度思考,AbstractList是不可见的,只是起到抽取公共方法的作用,最好不要加在这里

AbstractList抽象类代码如下:
在这里插入图片描述
最后让LinkedList继承AbstractList并实现相应方法(方法体先留空),如下:
在这里插入图片描述

(三)动态数组(Dynamic Array)的优化

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

    private E[] elements;//所有的元素
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 指定数组的容量
     *
     * @param capaticy 容量
     */
    public ArrayList(int capaticy) {
        capaticy = (capaticy < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capaticy;
        elements = (E[]) new Object[capaticy];
    }

    /**
     * 不指定的话默认容量是10
     */
    public ArrayList() {
        this(DEFAULT_CAPACITY);
    }


    /**
     * 保证要有capacity的容量
     *
     * @param capacity
     */
    private void ensureCapacity(int capacity) {
        int oldCapacity = elements.length;
        if (oldCapacity >= capacity) return;//不需要扩容,直接返回
        //位运算比浮点运算(oldCapacity * 2)效率高
        int newCapacity = oldCapacity + (oldCapacity >> 1);//相当于乘1.5(每次扩容1.5倍)
        E[] newElements = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newElements[i] = elements[i];
        }
        elements = newElements;//旧的数组指向新的数组
        System.out.println(oldCapacity + "扩容为:" + newCapacity);
    }

    /**
     * 清除所有元素
     */
    @Override
    public void clear() {
        for (int i = 0; i < size; i++) {
            elements[i] = null;
        }
        size = 0;
    }

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

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


    /**
     * 获取index位置的元素
     *
     * @param index
     * @return
     */
    @Override
    public E get(int index) {
        rangeCheck(index);
        return elements[index];
    }

    /**
     * 设置index位置的元素
     *
     * @param index
     * @param element
     * @return 原来的元素ֵ
     */
    @Override
    public E set(int index, E element) {
        rangeCheck(index);
        E old = elements[index];
        elements[index] = element;
        return old;
    }

    /**
     * 在index位置插入一个元素
     *
     * @param index
     * @param element
     */
    @Override
    public void add(int index, E element) {
        if (element == null) return;
        rangeCheckForAdd(index);
        ensureCapacity(size + 1);//capacity取值为size+1,就是保证比size多一个,不怕不够用
        for (int i = size; i > index; i--) {
            elements[i] = elements[i - 1];
        }
        elements[index] = element;
        size++;
    }

    /**
     * 删除index位置的元素
     *
     * @param index
     * @return
     */
    @Override
    public E remove(int index) {
        rangeCheck(index);
        E old = elements[index];
        for (int i = index + 1; i < size; i++) {
            elements[i - 1] = elements[i];
        }
        elements[--size] = null;
        return old;
    }

    /**
     * 查看元素的索引
     *
     * @param element
     * @return
     */
    @Override
    public int indexOf(E element) {
        if (element == null) {
            for (int i = 0; i < size; i++) {
                if (elements[i] == null) return i;
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (element.equals(elements[i])) return i;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();//使用StringBuilder拼接字符串效率会更高
        stringBuilder.append("size=").append(size).append(",[");
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                stringBuilder.append(", ");
            }
            stringBuilder.append(elements[i]);
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
}

(四)链表(Linked List)的实现:clear

clear()方法清空所有元素,过程如下:
在这里插入图片描述
注意:不需要将next设置为null,因为first=null后,0号元素第一个被销毁,接着是1号元素,直至所有元素被销毁

代码如下:

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

(五)链表(Linked List)的实现:add

add(int index,E element)方法添加元素,过程如下:
在这里插入图片描述
现在有一个元素,想添加到1号元素的位置
在这里插入图片描述
首先让新元素指向1号元素
在这里插入图片描述
然后找到1号元素的前一个元素,让它指向新元素
在这里插入图片描述
最后索引发生调整
在这里插入图片描述
最后size+1,添加完成
在这里插入图片描述
首先要写一个私有方法,private Node<E> node(int index),实现根据index获取对应的Node,代码如下:

    private Node<E> node(int index) {
        rangeCheck(index);
        Node<E> node = first;
        //从first开始,next index次 就可以找到要找的结点
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

public void add(int index, E element)方法的代码如下:

    @Override
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        //要特殊处理index=0的情况
        if (index == 0) {
            first = new Node<>(element, first);
        } else {
            Node<E> prev = node(index - 1);
            prev.next = new Node<>(element, prev.next);
        }
        size++;
    }

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

(六)链表(Linked List)的实现:get&set

借助于刚才写的private Node<E> node(int index)方法可以很简单的实现get()set()方法,代码如下:

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

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

注意get()set()方法都调用了private Node<E> node(int index)方法,该方法内部已经做了索引的检查,所以无需再检查索引

(七)链表(Linked List)的实现:remove

remove(int index)方法删除元素,过程如下:
比如想要删除index=1的元素
在这里插入图片描述
直接让0号元素指向2号元素
在这里插入图片描述
此时1号元素就会被销毁了
在这里插入图片描述
最后调整元素序号,删除完成
在这里插入图片描述
代码如下:

    @Override
    public E remove(int index) {
        Node<E> node = first;
        if (index == 0) {
            first = first.next;
        } else {
            Node<E> prev = node(index - 1);
            node = prev.next;
//            prev.next = prev.next.next;
            prev.next = node.next;
        }
        size--;
        return node.element;
    }

(八)链表(Linked List)的实现:indexOf

    @Override
    public int indexOf(E element) {
        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 (element.equals(node.element)) return i;
                node = node.next;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

(九)链表(Linked List)的代码汇总&测试

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

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

    @Override
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        //要特殊处理index=0的情况
        if (index == 0) {
            first = new Node<>(element, first);
        } else {
            Node<E> prev = node(index - 1);
            prev.next = new Node<>(element, prev.next);
        }
        size++;
    }

    private Node<E> node(int index) {
        rangeCheck(index);
        Node<E> node = first;
        //从first开始,next index次 就可以找到要找的结点
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

    @Override
    public E remove(int index) {
        Node<E> node = first;
        if (index == 0) {
            first = first.next;
        } else {
            Node<E> prev = node(index - 1);
            node = prev.next;
//            prev.next = prev.next.next;
            prev.next = node.next;
        }
        size--;
        return node.element;
    }

    @Override
    public int indexOf(E element) {
        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 (element.equals(node.element)) return i;
                node = node.next;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();//使用StringBuilder拼接字符串效率会更高
        stringBuilder.append("size=").append(size).append(",[");
        Node<E> node = first;
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                stringBuilder.append(", ");
            }
            stringBuilder.append(node.element);
            node = node.next;
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
}

测试如下:
在这里插入图片描述

(十)补充

完善remove()方法
在这里插入图片描述
修改后如下:
在这里插入图片描述
注意:后面的node(int index)虽然会调用rangeCheck(int index),但是在那之前就有抛出异常的风险,不同于get()set()方法一开始就调用node(int index)

(十一)虚拟头结点

我们之前的add(int index, E element)remove(int index)方法都对index=0做了特殊处理,如下:
在这里插入图片描述
在这里插入图片描述
有时候为了让代码更加精简,统一所有节点的处理逻辑,可以在最前面增加一个虚拟的头结点(不存储数据)
在这里插入图片描述
首先要增加一个构造函数,如下:

    public LinkedList2() {
        first = new Node<>(null, null);
    }

然后修改Node<E> node(int index)方法,如下:
在这里插入图片描述
修改add(int index, E element)方法,如下:

    @Override
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        //当index=0时,会过不了node方法里面的索引判断,所以也要特殊处理
        Node<E> prev = (index == 0) ? first : node(index - 1);
        prev.next = new Node<>(element, prev.next);
        size++;
    }

修改remove(int index)方法,如下:

    @Override
    public E remove(int index) {
        rangeCheck(index);
        Node<E> prev = (index == 0) ? first : node(index - 1);
        Node<E> node = prev.next;
        prev.next = node.next;
        size--;
        return node.element;
    }

最后修改toString()方法,如下:
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章