Bootstrap

单链表的原理与实现

单链表

单链表在内存中不像数组那样,拥有连续的内存空间,内存空间是碎片化的
单链表主要是在每个节点中保存下一个节点的内存地址,便于获取下一个节点的内容
在这里插入图片描述
添加的时候,直接找到要添加的位置的前一个节点,然后将引用指向要添加的内容
例如在下标为1的位置添加一个59
在这里插入图片描述
只需要找到下标为0的位置的节点,将该节点的下一个引用指向新的节点,然后新的节点的下一个引用指向原来下标为1的节点。
不需要像动态数组那样,将后面所有的节点向后移动一个位置

public class SingleLinkedList<E> {

    private static final int ELEMENT_NOT_FOUNT = -1;

    private int size = 0;
    private Node<E> head;

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

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

    /**
     * 清除所有元素
     */
    public void clear() {
        head = null;
        size = 0;
    }

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

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

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

    /**
     * 添加元素到尾部
     *
     * @param element
     */
    public void add(E element) {
        add(size, element);
    }

    /**
     * 获取index位置的元素
     *
     * @param index
     * @return
     */
    public E get(int index) {
        return node(index).element;
    }

    /**
     * 设置index位置的元素
     * 获取到该位置的节点,直接覆盖原来的值
     *
     * @param index
     * @param element
     * @return 原来的元素ֵ
     */
    public E set(int index, E element) {
        Node<E> node = node(index);
        E oldElement = node.element;
        node.element = element;
        return oldElement;
    }

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

        if (index == 0) { // 从0位置插入,需要将头指向新的节点,新节点的下一个引用为原来的头
            head = new Node<>(element, head);
        } else { // 从其他位置插入,获取要插入位置的前一个节点,将前一个节点的引用指向新的节点,新的节点的引用指向原来index位置的节点
            Node<E> prev = node(index - 1);
            prev.next = new Node<>(element, prev.next);
        }
        size++;
    }


    /**
     * 删除index位置的元素
     *
     * @param index
     * @return
     */
    public E remove(int index) {
        checkRange(index);
        Node<E> node = head;
        if (index == 0) { // 删除第一个节点,直接将头节点指向原来头节点的下一个
            head = head.next;
        } else { // 删除其他节点,获取要删除节点的前一个节点,该节点的引用指向下下(两个)个节点
            Node<E> prev = node(index - 1);
            node = prev.next;
            prev.next = node.next;
        }
        size--;
        return node.element;
    }

    /**
     * 查看元素的索引
     *
     * @param element
     * @return
     */
    public int indexOf(E element) {
        if (element == null) { // 处理传入的参数为null的情况
            Node<E> node = head;
            for (int i = 0; i < size; i++) {
                if (node.element == null) {
                    return i;
                }
                node = node.next;
            }
        } else {
            Node<E> node = head;
            for (int i = 0; i < size; i++) {
                if (element.equals(node.element)) { // 节点可能存储null,所以用传入的element调用equals方法,避免出现空指针异常
                    return i;
                }
                node = node.next;
            }
        }

        return ELEMENT_NOT_FOUNT;
    }

    /**
     * 获取对应下标的节点
     * @param index
     * @return
     */
    private Node<E> node(int index) { // 遍历找到对应下标的节点,找到index的节点,循环执行的次数为index - 1次
        checkRange(index);
        Node<E> node = head;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

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

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

    private void checkRangeForAdd(int index) {
        if (index < 0 || index > size) {
            outOfBounds(index);
        }
    }

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

        return sb.toString();
    }

    public static void main(String[] args) {
        SingleLinkedList<Integer> list = new SingleLinkedList<>();

        list.add(11);
        list.add(22);
        list.add(33);
        list.add(44); // 11 22 33 44

        list.add(1, 55); // 11 55 22 33 44
        list.remove(0); // 55 22 33 44
        list.set(2, 66); // 55 22 66 44

        System.out.println(list.indexOf(22));
        System.out.println(list);
    }
}

循环单链表

将单链表修改成循环单链表,只需要将链表中最后一个节点的引用指向头节点。
在单链表的基础上只需要修改添加节点和删除节点的方式
主要体现在对头节点的引用上,其他的并没有改变

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

    if (index == 0) { // 从0位置插入,需要将头指向新的节点,新节点的下一个引用为原来的头
        Node<E> newHead = new Node<>(element, head);
        // 当链表为空时,将头指向新节点,新节点的引用指向其本身
        Node<E> last = (size == 0) ? newHead : node(size - 1);
        last.next = newHead;
        head = newHead;
    } else { // 从其他位置插入,获取要插入位置的前一个节点,将前一个节点的引用指向新的节点,新的节点的引用指向原来index位置的节点
        Node<E> prev = node(index - 1);
        prev.next = new Node<>(element, prev.next);
    }
    size++;
}


/**
 * 删除index位置的元素
 *
 * @param index
 * @return
 */
public E remove(int index) {
    checkRange(index);
    Node<E> node = head;
    if (index == 0) { // 删除第一个节点,直接将头节点指向原来头节点的下一个
        if (size == 1) {
            head = null;
        } else {
            Node<E> last = node(size - 1);
            head = head.next;
            last.next = head;
        }
    } else { // 删除其他节点,获取要删除节点的前一个节点,该节点的引用指向下下(两个)个节点
        Node<E> prev = node(index - 1);
        node = prev.next;
        prev.next = node.next;
    }
    size--;
    return node.element;
}
;