Java手搓数据结构:从零模拟实现单向无头非循环链表

📚 目录

1.单向无头非循环链表简单介绍

链表我们可以理解为:一辆火车,每节火车车厢里面存储着内容和下一个车厢的节点。

无头单向非循环链表,是链表最基础的一种形态:

  • 无头:链表没有额外的头结点,第一个节点就是数据节点本身;
  • 单向:每个节点只保存一个指向下一个节点的引用,只能从前往后遍历;
  • 非循环:最后一个节点的next为null,不指向链表的开头。

[🔙 返回目录](#🔙 返回目录)


2.链表属性

接下来我们以整型单向无头链表为例子:   链表所需要的属性:   可以定义接口的,也可以不定义接口。

以定义接口为例子:

java 复制代码
public interface IMyList {
    //头插
    void addFirst(int data);
    //尾插
    void addLast(int data);
    //随便插
    void addIndex(int index,int val);
    //获取链表大小
    int size();
    //清除
    void clear();
    //删除
    void remove(int kay);
    //删除所以有关这个值
    void removeAll(int kay);
    //查找
    boolean contains(int data);
    //打印内容
    void display();
}

然后我们需要创建一个MyLink类,里面有一个静态内部类LinkNode也就是我们的节点。

和一个头节点。

java 复制代码
public class MyLink implements ILink{

	//节点
    static class LinkNode{
        private int val;
        private LinkNode next;
        public LinkNode(int val) {
            this.val = val;
        }
    }
	//头节点
    public LinkNode head;
    //---------------------头插
    @Override
    public void addFirst(int val) {

    }
    //---------------------尾插
    @Override
    public void addLast(int val) {

    }
    //---------------------任意位置
    @Override
    public void addIndex(int index, int val) {

    }
    //---------------------链表大小
    @Override
    public int size() {
        
    }
    //---------------------清空链表
    @Override
    public void clear() {

    }
    //---------------------删除链表一个相关节点
    @Override
    public void remove(int key) {

    }
    //---------------------删除所有相关的节点
    @Override
    public void removeAll(int key) {

    }
    //---------------------查找
    @Override
    public boolean contains(int val) {
        return false;
    }
    //---------------------打印
    @Override
    public void display() {

    }
}

[🔙 返回目录](#🔙 返回目录)


3.模拟实现链表

头插

有4个节点,我们需要一次进行头插。

需要让添加的 数据的下一个为head,然后再让head走到当前要添加的位置。

java 复制代码
    public void addFirst(int val) {
        LinkNode cur = new LinkNode(val);
        cur.next = head;+
        head = cur;
    }

此时我们能看到我们的头插没有任何问题。

尾插

假设有4个节点。

此时我们需要判断此时链表是否为第一个元素,如果为第一个元素就让head = 我们要尾插的节点。

后面需要通过循环找到当前链表最后一个元素,让最后一个元素尾插。

java 复制代码
    public void addLast(int val) {
        LinkNode linkNode = new LinkNode(val);
        if(head==null) {
            head = linkNode;
            return;
        }
        //此时链表不是空,我们就需要找到尾节点的位置。
        LinkNode cur = getLastIndex();
        cur.next = linkNode;
    }
    //专门服务于addLast的方法
    private LinkNode getLastIndex() {
        //定义一个零时的节点来找最后一个节点
        LinkNode cur = head;
        while (cur.next!=null) {//只要节点的下一个不为null就让他进去遍历。
            cur = cur.next;
        }
        //直到找到最后一个带有数据的节点。
        return cur;
    }

此时链表成功倒插。

任意位置插入

此时有一个节点想要插入到2的位置。

首先我们得检查次链表的合法性:是不是为空?插入的位置是否合法?

添加的自定义异常:用来判断是否合法。

java 复制代码
public class LinkLegal extends RuntimeException{
    public LinkLegal() {
    }

    public LinkLegal(String message) {
        super(message);
    }
}
java 复制代码
    public void addIndex(int index, int val) {
        //判断是否合法,此时我们需要自定义一个异常。
        legal(index);
        //此时我们需要判断是否为头插
        if(index == 0) {
            addFirst(val);
            return;
        }
        //此时判断是不是尾插
        if(index == size()) {
            addLast(val);
            return;
        }

        //定义要插入的元素;
        LinkNode linkNode = new LinkNode(val);
        //此时既不是头插也不是尾插
        LinkNode cur = getIndexPrevious(index);
        //让定义的下一个元素等于cur的下一个
        linkNode.next = cur.next;
        cur.next = linkNode;

    }
    //专门为addIndex服务的方法
    private void legal(int index) {
        if(index>size()||index<0) {
            throw new LinkLegal("下标不合法");
        }
    }
    //专门帮cur寻找插入位置前一个节点
    private LinkNode getIndexPrevious(int index) {
        int count = 0;
        LinkNode cur = head;
        while (count!=index-1) {
            cur = cur.next;
            count++;
        }
        return cur;
    }

结果看来:我们的也是正确的。

链表长度

求链表长度就比较简单了,我们会遍历链表就行。

我们只要需要定义一个临时变量:count每次cur不等于null就++;

java 复制代码
    public int size() {
        LinkNode cur = head;
        int count = 0;
        while (cur!=null) {
            count++;
            cur = cur.next;
        }
        return count;
    }

链表的打印

和求长度一样,需要我们变量链表。

java 复制代码
    public void display() {
        LinkNode cur = head;
        while (cur!=null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

移除指定值的第一个节点。

我们需要找到删除的前一个节点,再定义一个节点来保存删除节点的下一个节点。

java 复制代码
    public void remove(int key) {
        //判断链表为不为null
        if(head==null) {
            System.out.println("链表为空");
            return;
        }
        //判断要删除的节点是否为头部
        if(head.val==key) {
            head = head.next;
            return;
        }
        //此时我们需要找到删除节点的前一个几点。
        LinkNode cur = getKeyPrevious(key);
        //判断当前值是否为null
        if(cur==null|| cur.next==null) {
            System.out.println("没有你要删除的元素");
            return;
        }
        //此时找到了当前这个元素。
        //此时我们需要一个新的LinkNode类型的pre
        LinkNode pre = cur.next;
        //让cur的下一个等于pre
        cur.next = pre.next;

    }
    //专门服务于remove这个方法
    private LinkNode getKeyPrevious(int key) {
        LinkNode cur = head;
        while (cur.next!=null) {
            //找到要删除的前一个元素
            if(cur.next.val==key) {
            		//如果是可以返回删除节点的前一个节点
                return cur;
            }
            cur = cur.next;
        }
        //找不到则返回null
        return null;
    }

删除头删除尾删除中间都没有问题。

删除所有相关的节点

假设我们要删除链表中所有值为1的节点。

首先我们还是得要判断head的合法性。

我们需要定义一个cur等于head的下一个节点,再顶一个一个pre等于head。

java 复制代码
    public void removeAll(int key) {
        if(head==null) {
            System.out.println("链表为空");
            return;
        }
        LinkNode pre = head;
        LinkNode cur = head.next;
        while (cur!=null) {
            if(cur.val==key) {
               pre.next = cur.next;
            }else {
               pre = cur;
            }
            cur = cur.next;
        }
        //最后再判断链表的头部是否为我们要删除的节点
        if(head.val==key) {
            head = head.next;
        }
    }

结果来看我们也是成功模拟实现了。

查找

查找也是相较于添加和删除的比较简单。

java 复制代码
    public boolean contains(int val) {
        LinkNode cur = head;
        while (cur!=null) {
            if(cur.val==val) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

清空链表

和顺序表类似,我们需要让每一个节点都置为null。

java 复制代码
    public void clear() {
        LinkNode cur = head;
        while (cur!=null) {
            LinkNode pre = cur.next;
            cur.next = null;
            cur = pre;
        }
        head = null;
    }

[🔙 返回目录](#🔙 返回目录)


4. 代码汇总

java 复制代码
public interface ILink {
    //头插
    void addFirst(int val);
    //尾插
    void addLast(int val);
    //随便插
    void addIndex(int index,int val);
    //获取链表大小
    int size();
    //清除
    void clear();
    //删除
    void remove(int key);
    //删除所以有关这个值
    void removeAll(int key);
    //查找
    boolean contains(int val);
    //打印内容
    void display();
}
java 复制代码
public class LinkLegal extends RuntimeException{
    public LinkLegal() {
    }

    public LinkLegal(String message) {
        super(message);
    }
}
java 复制代码
public class MyLink implements ILink{

    static class LinkNode{
        private int val;
        private LinkNode next;
        public LinkNode(int val) {
            this.val = val;
        }
    }

    public LinkNode head;
    //---------------------头插
    @Override
    public void addFirst(int val) {
        LinkNode linkNode = new LinkNode(val);
        linkNode.next = head;
        head = linkNode;
    }
    //---------------------尾插
    @Override
    public void addLast(int val) {
        LinkNode linkNode = new LinkNode(val);
        if(head==null) {
            head = linkNode;
            return;
        }
        //此时链表不是空,我们就需要找到尾节点的位置。
        LinkNode cur = getLastIndex();
        cur.next = linkNode;
    }
    //专门服务于addLast的方法
    private LinkNode getLastIndex() {
        //定义一个零时的节点来找最后一个节点
        LinkNode cur = head;
        while (cur.next!=null) {
            cur = cur.next;
        }
        return cur;
    }
    //---------------------任意位置
    @Override
    public void addIndex(int index, int val) {
        //判断是否合法,此时我们需要自定义一个异常。
        legal(index);
        //此时我们需要判断是否为头插
        if(index == 0) {
            addFirst(val);
            return;
        }
        //此时判断是不是尾插
        if(index == size()) {
            addLast(val);
            return;
        }

        //定义要插入的元素;
        LinkNode linkNode = new LinkNode(val);
        //此时既不是头插也不是尾插
        LinkNode cur = getIndexPrevious(index);
        //让定义的下一个元素等于cur的下一个
        linkNode.next = cur.next;
        cur.next = linkNode;

    }
    //专门为addIndex服务的方法
    private void legal(int index) {
        if(index>size()||index<0) {
            throw new LinkLegal("下标不合法");
        }
    }
    //专门帮cur寻找插入位置前一个节点
    private LinkNode getIndexPrevious(int index) {
        int count = 0;
        LinkNode cur = head;
        while (count!=index-1) {
            cur = cur.next;
            count++;
        }
        return cur;
    }

    //---------------------链表大小
    @Override
    public int size() {
        LinkNode cur = head;
        int count = 0;
        while (cur!=null) {
            count++;
            cur = cur.next;
        }
        return count;
    }
    //---------------------清空链表
    @Override
    public void clear() {
        LinkNode cur = head;
        while (cur!=null) {
            LinkNode pre = cur.next;
            cur.next = null;
            cur = pre;
        }
        head = null;
    }
    //---------------------删除链表一个节点
    @Override
    public void remove(int key) {
        //判断链表为不为null
        if(head==null) {
            System.out.println("链表为空");
            return;
        }
        //判断要删除的节点是否为头部
        if(head.val==key) {
            head = head.next;
            return;
        }
        //此时我们需要找到删除节点的前一个几点。
        LinkNode cur = getKeyPrevious(key);
        //判断当前值是否为null
        if(cur==null|| cur.next==null) {
            System.out.println("没有你要删除的元素");
            return;
        }
        //此时找到了当前这个元素。
        //此时我们需要一个新的LinkNode类型的pre
        LinkNode pre = cur.next;
        //让cur的下一个等于pre
        cur.next = pre.next;

    }
    //专门服务于remove这个方法
    private LinkNode getKeyPrevious(int key) {
        LinkNode cur = head;
        while (cur.next!=null) {
            //找到要删除的前一个元素
            if(cur.next.val==key) {
                return cur;
            }
            cur = cur.next;
        }
        //找不到则返回null
        return null;
    }


    //---------------------删除所有相关的节点
    @Override
    public void removeAll(int key) {
        if(head==null) {
            System.out.println("链表为空");
            return;
        }
        LinkNode pre = head;
        LinkNode cur = head.next;
        while (cur!=null) {
            if(cur.val==key) {
               pre.next = cur.next;
            }else {
               pre = cur;
            }
            cur = cur.next;
        }
        //最后再判断链表的头部是否为我们要删除的节点
        if(head.val==key) {
            head = head.next;
        }
    }
    //---------------------查找
    @Override
    public boolean contains(int val) {
        LinkNode cur = head;
        while (cur!=null) {
            if(cur.val==val) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
    //---------------------打印
    @Override
    public void display() {
        LinkNode cur = head;
        while (cur!=null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }
}
java 复制代码
public class Test {
    public static void main(String[] args) {
        MyLink myLink = new MyLink();
        myLink.addLast(1);
        myLink.addLast(2);
        myLink.addLast(1);
        myLink.addLast(4);
        myLink.addIndex(1,123);
        myLink.remove(2);
        myLink.removeAll(1);
        System.out.println(myLink.contains(1));
        System.out.println("清空前");
        myLink.display();
        myLink.clear();
        System.out.println("清空后");
        myLink.display();
    }
}

[🔙 返回目录](#🔙 返回目录)


相关推荐
0xDevNull1 小时前
队列(Queue)实战教程:从原理到架构应用
java·开发语言·后端
y = xⁿ1 小时前
MySQL学习日记:关于MVCC及一些八股总结
数据库·学习·mysql
再写一行代码就下班2 小时前
word模版导出(占位符方式)
java·开发语言·word
懒得起名_yyf2 小时前
AI智能体Skills全面入门指南
java
~无忧花开~2 小时前
CSS全攻略:从基础到实战技巧
开发语言·前端·css·学习·css3
敖正炀2 小时前
集合-List-ArrayList
java
BING_Algorithm2 小时前
JDBC核心教程
java·后端·mysql
京师20万禁军教头2 小时前
33面向对象(中级)-object类详解
java
一个小浪吴啊2 小时前
重构 AI 编程流:基于 Hermes 记忆中枢与 OpenCode 执行终端的 Harness 工程化实践
java·人工智能·opencode·harness·hermes