一、前言

链表是一个线性的动态的数据结构,不需要处理固定容量问题,当时却丧失了随机访问的能力。链表数据存储在节点(Node)中,可以辅助组成其它的数据结构,如哈希表

二、实现

  • 链表实现
/**
 * 链表实现
 * @param <E>
 */
public class LinkedList<E> {
    /**
     * 虚拟头节点
     */
    private Node dummyHead;

    /**
     * 链表中元素个数
     */
    private int size;

    public LinkedList() {
        dummyHead = new Node(null,null);
        size = 0;
    }

    /**
     * 获取链表中元素个数
     * @return
     */
    public int getSize() {
        return size;
    }

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

    /**
     * 在链表的index位置添加新的元素
     * 在链表中不是一个常用的操作,练习用
     * @param index 索引位置
     * @param e 元素
     */
    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("add failed. illegal index.");
        }

        //找到插入位置的前一个节点
        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            prev = prev.next;
        }
        //插入元素
        prev.next = new Node(e, prev.next);
        size++;
    }

    /**
     * 在链表头添加元素
     * @param e 元素
     */
    public void addFirst(E e) {
        add(0, e);
    }

    /**
     * 在链表末尾添加新的元素
     * @param e 元素
     */
    public void addLast(E e) {
        add(size, e);
    }

    /**
     * 获取链表指定位置的元素
     * 非常用操作,练习用
     * @param index 指定位置
     * @return 元素
     */
    public E get(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("get failed, illegal index.");
        }

        Node cur = dummyHead.next;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        return cur.e;
    }

    /**
     * 获取链表第一个元素
     * @return
     */
    public E getFirst() {
        return get(0);
    }

    /**
     * 获取链表最后一个元素
     * @return
     */
    public E getLast() {
        return get(size - 1);
    }

    /**
     * 更新链表指定位置的元素
     * 非常用操作,练习用
     * @param index 指定位置
     * @return 元素
     */
    public void set(int index, E e) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("set failed, illegal index.");
        }

        Node cur = dummyHead.next;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }

        cur.e = e;
    }

    /**
     * 查找链表中是否存在元素e
     * @param e 元素
     * @return
     */
    public boolean contains(E e) {
        Node cur = dummyHead.next;
        while (cur != null) {
            if (cur.e.equals(e)) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    /**
     * 删除链表指定位置的元素
     * 非常用操作,练习用
     * @param index 指定位置
     * @return 元素
     */
    public E remove(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("remove failed, illegal index.");
        }
        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            prev = prev.next;
        }

        Node removeNode = prev.next;
        prev.next = removeNode.next;
        removeNode.next = null;
        size--;
        return removeNode.e;
    }

    /**
     * 移除头部元素
     * @return
     */
    public E removeFirst() {
        return remove(0);
    }

    public E removeLast() {
        return remove(size - 1);
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        Node cur = dummyHead.next;
        while (cur != null) {
            res.append(cur + "->");
            cur = cur.next;
        }
        res.append("NULL");
        return res.toString();
    }

    /**
     * 节点
     */
    private class Node {
        public E e;
        public Node next;

        public Node(E e,Node next) {
            this.e = e;
            this.next = next;
        }

        public Node(E e) {
            this(e, null);
        }

        public Node() {
            this(null, null);
        }

        @Override
        public String toString() {
            return e.toString();
        }
    }


}

三、链表的应用

1、力扣203问题:移除链表元素

  • 方式一:遍历链表删除元素
/**
 * 力扣203. 移除链表元素(https://leetcode-cn.com/)
 * Definition for singly-linked list.
 * 示例:
 * 输入: 1->2->6->3->4->5->6, val = 6
 * 输出: 1->2->3->4->5
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        //虚拟节点
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;

        //遍历删除
        ListNode prev = dummyHead;
        while (prev.next != null) {
            if (prev.next.val == val) {
                ListNode removeNode = prev.next;
                prev.next = removeNode.next;
                removeNode.next = null;
            } else {
                prev = prev.next;
            }
        }
        return dummyHead.next;
    }
}
  • 方式二:递归链表
/**
 * 力扣203. 移除链表元素(https://leetcode-cn.com/)
 * Definition for singly-linked list.
 * 示例:
 * 输入: 1->2->6->3->4->5->6, val = 6
 * 输出: 1->2->3->4->5
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution2 {
    /**
     * 利用链表天然递归结构性质解决
     * @param head 链表
     * @param val 待删除值
     * @return
     */
    public ListNode removeElements(ListNode head, int val) {

        //不可拆分的基本问题
        if (head == null) {
            return null;
        }

        //拆分为更小的问题
        head.next = removeElements(head.next, val);
        return head.val == val ? head.next : head;
    }
}

四、复杂度分析

  • 添加操作O(n)

    1. addLast(e) => o(n) : 没有链尾节点的指针,需要遍历列表
    2. addFirst(e) => O(1) : 有链首节点指针,无需遍历
    3. add(index,e) => O(n/2)=O(n) : 需要遍历链表找到合适位置
  • 删除操作O(n)

    1. removeLast(e) => o(n) : 没有链尾节点的指针,需要遍历列表
    2. removeFirst(e) => O(1) : 有链首节点指针,无需遍历
    3. remove(index,e) => O(n/2)=O(n) : 需要遍历链表找到合适位置
  • 修改操作O(n)

    1. set(index,e) => o(n) : 需要遍历链表找到合适位置
  • 查找操作O(n)

    1. get(index) => o(n) : 需要从头遍历列表
    2. contains(e) => O(1) : 需要从头遍历列表

五、其它数据结构


neojayway
52 声望10 粉丝

学无止境,每天进步一点点