什么是单链表

单链表是链表的其中一种基本结构。它是构成单链表的基本结点结构。在结点中数据域用来存储数据元素,指针域用于指向下一个具有相同结构的结点。
因为只有一个指针结点,称为单链表。

顺序表与单链表的区别

顺序表

(顺序存储)

优点:可随机存取,存储密度高
缺点:要求大片连续空间,改变容量不方便

单链表

(链式存储)

优点:不要求大片连续空间,改变容量方便
缺点:不可随机存取,要耗费一定空间存放指针(地址)

单链表结构

public class SinglyLinkedList <T> {

    /**
     * 头结点
     */
    private Node head;

    /**
     * 链表长度
     */
    private int size;

    /**
     * 结点
     */
    class Node {

        /**
         * 结点数据
         */
        private T t;

        /**
         * 下一个结点
         */
        private Node next;

        Node(T t, Node next) {
            this.t = t;
            this.next = next;
        }

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

        @Override
        public String toString() {
            return "Node{" + t + '}';
        }
    }
}

单链表基本操作

插入

/**
* 在指定下标处插入一个结点
* @param t
* @param index
* @return
*/
public boolean insert(T t, int index) {

    if (index < 0 || index > size) {
        throw new IllegalArgumentException("非法下标参数");
    }

    Node node = new Node(t);
    // 链表头部插入结点
    if (index == 0) {
        node.next = this.head;
        this.head = node;
        this.size++;
        return true;
    }

    // 找到要插入结点的前一个结点
    Node preNode = getNode(index - 1);
    // 前一个结点的下一个结点地址赋给当前结点的next
    node.next = preNode.next;
    // 当前结点的地址赋给前一个结点的next
    preNode.next = node;
    this.size++;
    return true;
}

这里的插入是一个后插操作,既在某个结点之后插入一个结点,因为结点中存有下一个结点的地址,所以其后面的结点都很好寻找和操作了。
对应的还有前插操作,既在某个结点之前插入一个结点,这要求得拿到前一个结点,才能改变前结点的next指向。
要拿到前一个结点,可以从头结点开始,依次寻找,有了头结点就等于有了整个单链表。
如果没有头结点,就无法拿到前一个结点。此时只能对当前结点使用后插操作,在交换两个结点当中的数据,变相地实现了前插操作。

删除

/**
 * 根据下标删除结点
 * @param index
 * @return
 */
public Node remove(int index) {

    if (index < 0 || index > size) {
        throw new IllegalArgumentException("非法下标参数");
    }
    Node removeNode = null;
    // 删除头结点
    if (index == 0) {
        removeNode = this.head;
        // 把头结点的下一结点作为头节点
        this.head = head.next;
        this.size--;
        return removeNode;
    }
    // 找到删除结点的前一结点
    Node preNode = getNode(index - 1);
    // 把要删除的结点保存和返回
    removeNode = preNode.next;
    // 把前一结点的下下个结点地址赋值给前一结点的next
    preNode.next = preNode.next.next;
    this.size--;
    return removeNode;
}

/**
 * 根据数据删除结点
 * @param t
 * @return
 */
public Node remove(T t) {
    // 链表或元素为空 直接返回
    if (size == 0 || null == t) {
        return null;
    }

    Node removeNode = null;
    for (int i = 0; i < size; i++) {
        Node node = getNode(i);
        if (t == node.t) {
            removeNode = remove(i);
            break;
        }
    }
    return removeNode;
}

查找

/**
 * 根据下标获取一个结点
 * @param index
 * @return
 */
public Node getNode(int index) {

    if (index < 0 || index > size) {
        throw new IllegalArgumentException("非法下标参数");
    }
    Node curNode = this.head;
    // 下标为0 直接返回头结点
    if (index == 0) {
        return curNode;
    }
    for (int i = 0; i < index; i++) {
        curNode = curNode.next;
    }
    return curNode;
}

/**
 * 根据元素值返回对应下标
 * @param t
 * @return
 */
public Node getNode(T t) {

    // 从头结点开始
    Node curNode = this.head;
    // 当前结点不为空且结点数据不等于传入元素
    while (null != curNode && curNode.t != t) {
        // 当前结点后移
        curNode = curNode.next;
    }
    return curNode;
}

完整代码

/**
 * @description: 自定义单链表
 * @author: opoa
 * @create: 2021-02-28 20:21
 **/
public class SinglyLinkedList <T> {

    /**
     * 头结点
     */
    private Node head;

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

    /**
     * 结点
     */
    class Node {

        /**
         * 结点数据
         */
        private T t;

        /**
         * 下一个结点
         */
        private Node next;

        Node(T t, Node next) {
            this.t = t;
            this.next = next;
        }

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

        @Override
        public String toString() {
            return "Node{" + t + '}';
        }
    }

    public SinglyLinkedList() {
        this.head = null;
        this.size = 0;
    }

    /**
     * 根据下标获取一个结点
     * @param index
     * @return
     */
    public Node getNode(int index) {

        if (index < 0 || index > size) {
            throw new IllegalArgumentException("非法下标参数");
        }
        Node curNode = this.head;
        // 下标为0 直接返回头结点
        if (index == 0) {
            return curNode;
        }
        for (int i = 0; i < index; i++) {
            curNode = curNode.next;
        }
        return curNode;
    }

    /**
     * 根据元素值返回对应下标
     * @param t
     * @return
     */
    public Node getNode(T t) {

        // 从头结点开始
        Node curNode = this.head;
        // 当前结点不为空且结点数据不等于传入元素
        while (null != curNode && curNode.t != t) {
            // 当前结点后移
            curNode = curNode.next;
        }
        return curNode;
    }

    /**
     * 在指定下标处插入一个结点
     * @param t
     * @param index
     * @return
     */
    public boolean insert(T t, int index) {

        if (index < 0 || index > size) {
            throw new IllegalArgumentException("非法下标参数");
        }

        Node node = new Node(t);
        // 链表头部插入结点
        if (index == 0) {
            node.next = this.head;
            this.head = node;
            this.size++;
            return true;
        }

        // 找到要插入结点的前一个结点
        Node preNode = getNode(index - 1);
        // 前一个结点的下一个结点地址赋给当前结点的next
        node.next = preNode.next;
        // 当前结点的地址赋给前一个结点的next
        preNode.next = node;
        this.size++;
        return true;
    }

    /**
     * 使用头插法插入一个元素
     * @param t
     * @return
     */
    public boolean insertFirst(T t) {
        return insert(t, 0);
    }

    /**
     * 使用尾插法插入一个元素
     * @param t
     * @return
     */
    public boolean insertLast(T t) {
        return insert(t, size);
    }

    /**
     * 根据下标删除结点
     * @param index
     * @return
     */
    public Node remove(int index) {

        if (index < 0 || index > size) {
            throw new IllegalArgumentException("非法下标参数");
        }
        Node removeNode = null;
        // 删除头结点
        if (index == 0) {
            removeNode = this.head;
            // 把头结点的下一结点作为头节点
            this.head = head.next;
            this.size--;
            return removeNode;
        }
        // 找到删除结点的前一结点
        Node preNode = getNode(index - 1);
        // 把要删除的结点保存和返回
        removeNode = preNode.next;
        // 把前一结点的下下个结点地址赋值给前一结点的next
        preNode.next = preNode.next.next;
        this.size--;
        return removeNode;
    }

    /**
     * 根据数据删除结点
     * @param t
     * @return
     */
    public Node remove(T t) {
        // 链表或元素为空 直接返回
        if (size == 0 || null == t) {
            return null;
        }

        Node removeNode = null;
        for (int i = 0; i < size; i++) {
            Node node = getNode(i);
            if (t == node.t) {
                removeNode = remove(i);
                break;
            }
        }
        return removeNode;
    }

    /**
     * 返回当前链表长度
     * @return
     */
    public int length() {
        return this.size;
    }

    /**
     * 打印输出单链表
     * @return
     */
    @Override
    public String toString() {

        if (size == 0) {
            return null;
        }
        Node curNode = this.head;
        StringBuilder sb = new StringBuilder();
        while (null != curNode) {
            sb.append(curNode.t.toString())
              .append(" -> ");
            curNode = curNode.next;
        }
        sb.append("null");
        return sb.toString();
    }
}

参考资料

2020 王道考研 数据结构【单链表的定义】
2020 王道考研 数据结构【单链表的插入删除】
2020 王道考研 数据结构【单链表的查找】
Java基础--单链表的实现

Q.E.D.


The best thing you can do at work hard.