java实现一个单向链表

简单介绍下单向链表

单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始;链表是使用指针进行构造的列表;又称为结点列表,因为链表是由一个个结点组装起来的;其中每个结点都有指针成员变量指向列表中的下一个结点;

链表是由结点构成,head指针指向第一个成为表头结点,而终止于最后一个指向NULL的指针。

单向链表和双向链表简单对比下

jdk(目前所用8)中的LinkedList是用双向链表实现的
下图中,有头节点,尾结点,链表长度
在这里插入图片描述
其节点代码:

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

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

双向链表较于单向链表:

  1. 单向链表只暴露头结点,尾结点指向null,每个节点Node只存储一个数据data和一个指向下个节点的next
  2. 而双向链表暴露头节点和尾结点,尾结点指向头结点,每个节点Node存储一个数据data和一个指向上个节点的prev和下个节点的next
  3. 双向链表实现是采用二分法,效率比单向链表要高些
  4. 但是由于双向链表的每个节点都比单向链表多存储一个prev节点,所以占用的内存空间就大,因此实际使用中,单向链表的使用率要比双向链表高
  5. 链表的插入删除效率比数组快,查找的效率比数组慢,是由于他每次查找都是要遍历的
  6. 数组存储时用一块连续的内存,而链表不需要在物理上相连

如果还不是很清楚,请跟着我,亲自实现一个单向链表.

——好记性不如烂笔头,同样的对对我们小猿员也是,自己动手比单纯的阅读理解,更迅速,记忆更牢靠

今天就用java实现一个单向链表,包括链表的增删改查

整个单向链表类的结构

/**
 * @author qinlei
 * @description 自定义单向链表
 * @date 2021/2/1 9:49
 */
public class Linked<T> {
    /**
     * 向外暴露的头节点
     */
    private Node head;
    /**
     * 链表的长度
     */
    private int size;

    /**
     * 初始化链表
     */
    public Linked() {
        this.size = 0;
        this.head = null;
    }

    /**
     * 链表中的节点
     */
    private class Node {
        private Node next = null;//节点的引用,即下一个节点
        private T data;//节点对象

        public Node(T data) {
            this.data = data;
        }

        public Node(T data, Node next) {
            this.next = next;
            this.data = data;
        }
    }
	//some method 增删改查等

}

我简单画一个单向链表结构图

其实下图的是网上较多的
单向链表结构图

链表的遍历

  1. 链表本质上是一个线性表
  2. 顺序表(如数组)可以随机读取,由于其在逻辑及物理上是有顺序(索引)
  3. 链表是顺序表,其在逻辑层及物理层上都是无序的,可能存在于内存中的任何地方,因此在遍历时,必须从头开始。如:需要找到第i个元素,则需要从0开始遍历直至找到(i-1)个元素

链表的操作方法

对于增删,可以这么理解,你将单向链表想象成一条绳子,新增需要将在绳子某处剪断,将新增的绳子与剪断的绳子缝合,即3根有顺序的绳子连接

新增

新增整体的思路:

  1. 将新增的队列将新增的位置index找到
  2. 找到该位置的前节点和后节点
  3. 将该位置的前节点指向新增的节点,将该新增的节点指向后节点

这里就有个问题,当新增的是作为头结点时,会出现没有前节点的情况,所以,这种情况,要单独拿出来

代码:

/**
     * 向头部插入
     *
     * @param data
     */
    public void addFirstNode(T data) {
        Node node = new Node(data);
        //将node的next节点指向链表头结点
        node.next = this.head;
        //将该链表的头结点指向node
        this.head = node;
        //链表长度++
        this.size++;
    }

    /**
     * 链表尾部追加
     *
     * @param data
     */
    public void addLastNode(T data) {
        addNode(data, this.size);
    }

    /**
     * 向链表中间插入节点
     *
     * @param d
     */
    public void addNode(T d, int index) {
        //表明插入的范围是0~size  0表示头部插入  size表示尾部插入 当参数不合标准时抛出参数异常
        if (index < 0 || index > size) throw new IllegalArgumentException("index illegal !");
        if (index == 0) {
            addFirstNode(d);
            return;
        }
        //查找index在链表的位置
        Node preNode = head;//待插入节点的前节点
        for (int i = 0; i < index - 1; i++) {
            preNode = preNode.next;
        }
        Node next = preNode.next;//待插入节点的后节点
        Node node = new Node(d, next);
        preNode.next = node;
        size++;
    }

删除

删除整体思路:同新增一样需要找到前节点和后节点,独立出来头结点.

所有的删除都需要对链表进行校验

	/**
     * 校验链表是否存在(头存在/长度存在)
     *
     * @return
     */
    private boolean checkExist() {
        return Optional.ofNullable(head).isPresent() && size > 0;
    }
 /**
     * 删除节点
     *
     * @param data 要删除的节点数据
     */
    public void removeNode(T data) {
        if (!checkExist()) return;
        if (Objects.equals(head.data, data)) {
            head = head.next;
            size--;
            return;
        }
        Node tmp = head;
        //根据数据查找节点
        while (Optional.ofNullable(tmp).isPresent() && Optional.ofNullable(tmp.next).isPresent()) {
            if (Objects.equals(tmp.next.data, data)) {
                tmp.next = tmp.next.next;
                break;
            }
            tmp = tmp.next;
        }
    }

    /**
     * 删除头部节点
     */
    public void removeFirstNode() {
        if (!checkExist()) throw new NullPointerException("the linked is null");
        head = head.next;
        size--;
    }

    /**
     * 删除尾部节点
     */
    public void removeLastNode() {
        if (!checkExist()) throw new NullPointerException("the linked is null");
        if (size == 1) removeFirstNode();
        Node tmp = this.head;
        while (Optional.ofNullable(tmp).isPresent() && Optional.ofNullable(tmp.next).isPresent()) {
            if (!Optional.ofNullable(tmp.next.next).isPresent()) {
                tmp.next = null;
                size--;
            }
            tmp = tmp.next;
        }
    }

修改

	/**
     *
     * @param oldData 旧数据
     * @param newData 新数据
     * @return
     */
    public Linked<T> updateNode(T oldData,T newData){
        if (!checkExist()) throw new NullPointerException("the linked is null");
        Node tmp = head;
        while (Optional.ofNullable(tmp).isPresent()) {
            if (Objects.equals(tmp.data,oldData)) {
                tmp.data = newData;
            }
            tmp = tmp.next;
        }
        return this;
    }

总结下

其实纵观修改和删除,其核心就是通过遍历比对数据data,不能像数组那样通过索引来直接操作

完整代码

package com.ql;


import java.util.Objects;
import java.util.Optional;

/**
 * @author qinlei
 * @description 自定义单向链表
 * @date 2021/2/1 9:49
 */
public class Linked<T> {
    /**
     * 向外暴露的头节点
     */
    private Node head;
    /**
     * 链表的长度
     */
    private int size;

    /**
     * 每次初始化都是新创建的链表
     */
    public Linked() {
        this.size = 0;
        this.head = null;
    }

    /**
     * 链表中的节点
     */
    private class Node {
        private Node next = null;//节点的引用,即下一个节点
        private T data;//节点对象

        public Node(T data) {
            this.data = data;
        }

        public Node(T data, Node next) {
            this.next = next;
            this.data = data;
        }
        @Override
        public String toString() {
            return "Node{" +
                    "next=" + next +
                    ", data=" + data +
                    '}';
        }
    }

    /**
     * 向头部插入
     *
     * @param data
     */
    public void addFirstNode(T data) {
        Node node = new Node(data);
        //将node的next节点指向链表头结点
        node.next = this.head;
        //将该链表的头结点指向node
        this.head = node;
        //链表长度++
        this.size++;
    }

    /**
     * 链表尾部追加
     *
     * @param data
     */
    public void addLastNode(T data) {
        addNode(data, this.size);
    }

    /**
     * 向链表中间插入节点
     *
     * @param d
     */
    public void addNode(T d, int index) {
        //表明插入的范围是0~size  0表示头部插入  size表示尾部插入
        if (index < 0 || index > size) throw new IllegalArgumentException("index illegal !");
        if (index == 0) {
            addFirstNode(d);
            return;
        }
        //查找index在链表的位置
        Node preNode = head;//待插入节点的前节点
        for (int i = 0; i < index - 1; i++) {
            preNode = preNode.next;
        }
        Node next = preNode.next;//待插入节点的后节点
        Node node = new Node(d, next);
        preNode.next = node;
        size++;
    }

    /**
     * 删除节点
     *
     * @param data 要删除的节点数据
     */
    public void removeNode(T data) {
        if (!checkExist()) return;
        if (Objects.equals(head.data, data)) {
            head = head.next;
            size--;
            return;
        }
        Node tmp = head;
        //根据数据查找节点
        while (Optional.ofNullable(tmp).isPresent() && Optional.ofNullable(tmp.next).isPresent()) {
            if (Objects.equals(tmp.next.data, data)) {
                tmp.next = tmp.next.next;
                break;
            }
            tmp = tmp.next;
        }
    }

    /**
     * 删除头部节点
     */
    public void removeFirstNode() {
        if (!checkExist()) throw new NullPointerException("the linked is null");
        head = head.next;
        size--;
    }

    /**
     * 删除尾部节点
     */
    public void removeLastNode() {
        if (!checkExist()) throw new NullPointerException("the linked is null");
        if (size == 1) removeFirstNode();
        Node tmp = head;
        while (Optional.ofNullable(tmp).isPresent() && Optional.ofNullable(tmp.next).isPresent()) {
            if (!Optional.ofNullable(tmp.next.next).isPresent()) {
                tmp.next = null;
                size--;
            }
            tmp = tmp.next;
        }
    }

    /**
     *
     * @param oldData 旧数据
     * @param newData 新数据
     * @return
     */
    public Linked<T> updateNode(T oldData,T newData){
        if (!checkExist()) throw new NullPointerException("the linked is null");
        Node tmp = head;
        while (Optional.ofNullable(tmp).isPresent()) {
            if (Objects.equals(tmp.data,oldData)) {
                tmp.data = newData;
            }
            tmp = tmp.next;
        }
        return this;
    }

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

    /**
     * 判断是否包含该元素
     *
     * @param data 元素数据
     * @return 布尔 包含为true 否则为false
     */
    public boolean contains(T data) {
        if (!checkExist()) return false;
        Node tmp = head;
        while (Optional.ofNullable(tmp).isPresent()) {
            if (Objects.equals(tmp.data, data)) {
                return true;
            }
            tmp = tmp.next;
        }
        return false;
    }

    /**
     * 校验链表是否存在(头存在/长度存在)
     *
     * @return
     */
    private boolean checkExist() {
        return Optional.ofNullable(head).isPresent() && size > 0;
    }

    /**
     * 暴露出去 节点的长度
     *
     * @return
     */
    public int getSize() {
        return size;
    }

    @Override
    public String toString() {
        return "Linked{" +
                "head=" + head +
                '}';
    }
}

测试代码

package com.ql;

/**
 * @author qinlei
 * @description todo
 * @date 2021/2/2 11:41
 */
public class LinkedTest {
    public static void main(String[] args) {
        Linked<Integer> linked = new Linked<>();
        //测试头部添加
        linked.addFirstNode(5);
        System.out.println(linked);
        //测试尾部添加
        linked.addLastNode(1);
        System.out.println(linked);
        //测试中间添加
        linked.addNode(6,1);
        System.out.println(linked);
        //测试修改
        linked.updateNode(6,10);
        System.out.println(linked);
        //测试是否包含
        System.out.println(linked.contains(2));
        System.out.println(linked.contains(1));
        //测试删除头部数据
        linked.removeFirstNode();
        System.out.println(linked);
        //测试删除尾部数据
        linked.removeLastNode();
        System.out.println(linked);
        //测试删除任意位置数据
        linked.removeNode(6);
        System.out.println(linked);
    }

}

后记

当然,如果在新增及删除时,没有size这个变量,那么可以通过,带新增(删除)的前节点/后节点来通过数据data的比对操作

这样的操作也可以叫做虚拟节点操作

参考

本文的学习参考于:
Java基础–单链表的实现——唐·吉坷德

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qlanto

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值