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

📚 目录

  • [1. 无头双向非循环链表的介绍](#1. 无头双向非循环链表的介绍)

  • [2. 双向链表属性](#2. 双向链表属性)

  • [3. 模拟实现双向链表](#3. 模拟实现双向链表)

  • [4. 代码汇总](#4. 代码汇总)

1. 无头双向非循环链表的介绍

  无头双向非循环链表不像我们单向链表,只有一个方向,走过了就不能够再找回来。

  双向链表可以通过当前节点找到后面的节点,也能找到前面的节点,双向链表比单向链表更加灵活。

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


2. 双向链表属性

  无头双向肺循环链表和单链表基本上类似,主要区别就是当前节点能找到前后的节点。

  定义一个接口:

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 MyLinkedList implements ILink{
	//节点:
    static class LinkNode {
        public int val;
        public LinkNode prev;
        public LinkNode next;
        //构造方法
        public LinkNode(int val) {
            this.val = val;
        }
    }
    //头节点
    public LinkNode head;
    //尾节点
    public LinkNode last;

    //---------头插-----------
    @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) {
    }
    //----------删除所有在链表中的key值------------
    @Override
    public void removeAll(int key) {
    }
    //-----------判断当前值是否在链表中--------
    @Override
    public boolean contains(int val) {
    }
    //-----------打印链表-----------
    @Override
    public void display() {
    }
}

  以整型链表来实现
[🔙 返回目录](#🔙 返回目录)


3. 模拟实现双向链表

头插:


**  插入前我们得先判断是否是第一次插入,如果是第一次插入,就让头节点和尾节点都指向当前节点。**

java 复制代码
    public void addFirst(int val) {
        //创建节点
        LinkNode node = new LinkNode(val);
        //首先得判断当前插入是不是第一次插入,如果是就让头和尾巴都在同一个地方
        if(head == null) {
            head = node;
            last = node;
            return;
        }
        //此时不是第一次添加数据
        node.next = head;
        head.prev = node;
        head = node;
    }
java 复制代码
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.addFirst(1);
        myLinkedList.addFirst(2);
        myLinkedList.addFirst(3);
}

  此时,我们就完成了头插:

尾插:

  注意事项:
**  和头插一样,需要判断是不是第一次插入数据。
  直接让last节点的下一个指向要插入的节点。**

java 复制代码
    public void addLast(int val) {
        //创建节点
        LinkNode node = new LinkNode(val);
        //首先得判断当前插入是不是第一次插入,如果是就让头和尾巴都在同一个地方
        if(head == null) {
            head = node;
            last = node;
        }
        //此时不是第一次添加数据
        last.next = node;
        node.prev = last;
        last = node;
    }

  此时我们需要插入4个节点:来判断我们的尾插是否成功:

java 复制代码
public class Test {
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.addLast(1);
        myLinkedList.addLast(2);
        myLinkedList.addLast(3);
        myLinkedList.addLast(4);
    }
}    

  结果上看:没有任何问题。

任意位置插入:


**  如果是头插,就调用我们的头插的方法,如果是尾插就调用我们尾插的方法。
  如果是中间位置插入:
  首先让插入节点记住插入位置后面的节点,我们此时是双向链表,不需要找到插入位置前一个节点,只需要找到插入的节点即可。此时我们还要自定义一个异常来帮我们检查插入位置的合法性**

java 复制代码
public class PosExcep extends RuntimeException{

    public PosExcep() {
    }

    public PosExcep(String message) {
        super(message);
    }
}
java 复制代码
    public void addIndex(int index, int val) {
        //首先判断插入位置的合法性
        posLegal(index);
        //此时下标是合法的,再判断是不是头插,或者尾插
        if(index == 0) {
            addFirst(val);
            return;
        }
        if(index==size()) {
            addLast(val);
            return;
        }
        //此时既不是头插也不是尾插。
        LinkNode node = new LinkNode(val);
        //找到当前要插入的位置的那个值
        LinkNode cur = getIndexPos(index);
        node.next = cur;
        cur.prev.next = node;
        node.prev = cur.prev;
        cur.prev = node;

    }

  专门服务于当前插入的方法:

java 复制代码
    //定义一个专门判断下标是否合理的方法
    private void posLegal(int index) {
        if(index<0||index>size()) {
            throw new PosExcep("下标不合法");
        }
    }

  专门服务于当前插入的方法,获取当前插入位置。

java 复制代码
    private LinkNode getIndexPos(int index) {
        LinkNode cur = head;
        int count = 0;
        while (count<index) {
            cur = cur.next;
            count++;
        }
        return cur;
    }

  Test测试类:

java 复制代码
public class Test {
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.addLast(1);
        myLinkedList.addLast(2);
        myLinkedList.addLast(3);
        myLinkedList.addLast(4);
        myLinkedList.addIndex(0,99);
        myLinkedList.addIndex(5,100);
        myLinkedList.addIndex(3,666);
        myLinkedList.display();
    }
}    

  结果:

链表的打印:

  此时我们只需要遍历我们的链表:

java 复制代码
    public void display() {
    	//如果链表为空,则直接返回
        if(head==null) {
            System.out.println("链表为空");
            return;
        }
        //此时链表不位空,定义一个临时的cur节点来遍历head,防止head被改变。
        LinkNode cur = head;
        while (cur!=null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }
删除

  首先得先找到当前要删除的节点,让当前节点的前一个节点指向后一个节点。

  当前节点没有被引用系统就会回收掉。

  定义cur来遍历链表。

java 复制代码
    //-------------删除链表----------------
    @Override
    public void remove(int key) {
        //判断当前链表是否为空
        if(head == null) {
            System.out.println("链表为空");
            return;
        }
        LinkNode cur = head;
        while (cur!=null) {
            if(cur.val == key) {
                //判断是不是头节点
                if(head == cur) {
                    head = head.next;
                    if(head!=null) {
                        //防止 当前双向链表只有一个节点
                        head.prev = null;
                    }
                }else {
                    //此时就不是头节点
                    cur.prev.next = cur.next;
                    //尾节点
                    if(cur.next == null) {
                        last = last.prev;
                    }else {
                        //删除中间节点
                        cur.next.prev = cur.prev;
                    }
                }
                return;
            }
            cur = cur.next;
        }
    }

  Test测试类:

java 复制代码
public class Test {
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.addLast(1);
        myLinkedList.addLast(2);
        myLinkedList.addLast(3);
        myLinkedList.addLast(4);
        myLinkedList.addLast(5);
        myLinkedList.remove(1);
        myLinkedList.remove(3);
        myLinkedList.remove(5);
        myLinkedList.display();
    }

  结果上看,当前删除没有问题。

删除所有等于这个值的节点

  定义一个cur节点来遍历链表。

  和删除一个节点方法类似:只不过我们将return给去掉了。

java 复制代码
 public void removeAll(int key) {
        if(head == null) {
            System.out.println("链表为空");
            return;
        }
        LinkNode cur = head;
        while (cur!=null) {
            if(cur.val == key) {
                //判断是不是头节点
                if(head == cur) {
                    head = head.next;
                    if(head!=null) {
                        //防止 当前双向链表只有一个节点
                        head.prev = null;
                    }
                }else {
                    //此时就不是头节点
                    cur.prev.next = cur.next;
                    if(cur.next == null) {
                        last = last.prev;
                    }else {
                        //删除中间节点
                        cur.next.prev = cur.prev;
                    }
                }
            }
            cur = cur.next;
        }
    }

  Test测试类:

java 复制代码
public class Test {
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.addLast(2);
        myLinkedList.addLast(2);
        myLinkedList.addLast(3);
        myLinkedList.addLast(2);
        myLinkedList.addLast(5);
        myLinkedList.removeAll(2);
        myLinkedList.display();
    }
}    

  结果:

判断当前链表是否有这个值

  我们只需要遍历一遍这个链表,如果有这个值则返回true反之返回false。

java 复制代码
//-----------判断当前值是否在链表中--------
    @Override
    public boolean contains(int val) {
        if(head==null) {
            return false;
        }
        //此时链表不为空
        LinkNode cur = head;
        while (cur!=null) {
            if(cur.val == val) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
求链表长度
java 复制代码
    //---------求链表大小-----------
    @Override
    public int size() {
        int count = 0;
        LinkNode cur = head;
        while (cur!=null) {
            cur = cur.next;
            count++;
        }
        return count;
    }
清空链表

  首先还是得判断当前链表是否为null。

  然后通过cur遍历链表让每一个节点置为null,最后将头和尾都置为null。

java 复制代码
    public void clear() {
        if(head == null) {
            System.out.println("链表为空,不需要清空链表");
            return;
        }
        //此时链表不为空。
        LinkNode cur = head;
        while (cur != null) {
            LinkNode next = cur.next;
            // 断开当前节点的所有引用
            cur.prev = null;
            cur.next = null;
            cur = next;
        }
        // 最后把头尾指针置空
        head = null;
        last = null;
    }

  Test测试类:

java 复制代码
public class Test {
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.addLast(2);
        myLinkedList.addLast(2);
        myLinkedList.addLast(3);
        myLinkedList.clear();
        myLinkedList.display();
        System.out.println("==========");
    }
 }   

  结果:

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


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 PosExcep extends RuntimeException{

    public PosExcep() {
    }

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

    static class LinkNode {
        public int val;
        public LinkNode prev;
        public LinkNode next;
        //构造方法
        public LinkNode(int val) {
            this.val = val;
        }
    }
    //头节点
    public LinkNode head;
    //尾节点
    public LinkNode last;

    //---------头插-----------
    @Override
    public void addFirst(int val) {
        //创建节点
        LinkNode node = new LinkNode(val);
        //首先得判断当前插入是不是第一次插入,如果是就让头和尾巴都在同一个地方
        if(head == null) {
            head = node;
            last = node;
            return;
        }
        //此时不是第一次添加数据
        node.next = head;
        head.prev = node;
        head = node;
    }
    //---------尾插插-----------
    @Override
    public void addLast(int val) {
        //创建节点
        LinkNode node = new LinkNode(val);
        //首先得判断当前插入是不是第一次插入,如果是就让头和尾巴都在同一个地方
        if(head == null) {
            head = node;
            last = node;
        }
        //此时不是第一次添加数据
        last.next = node;
        node.prev = last;
        last = node;
    }
    //---------任意位置插-----------
    @Override
    public void addIndex(int index, int val) {
        //首先判断插入位置的合法性
        posLegal(index);
        //此时下标是合法的,再判断是不是头插,或者尾插
        if(index == 0) {
            addFirst(val);
            return;
        }
        if(index==size()) {
            addLast(val);
            return;
        }
        //此时既不是头插也不是尾插。
        LinkNode node = new LinkNode(val);
        //找到当前要插入的位置的那个值
        LinkNode cur = getIndexPos(index);
        node.next = cur;
        cur.prev.next = node;
        node.prev = cur.prev;
        cur.prev = node;

    }
    //定义一个专门判断下标是否合理的方法
    private void posLegal(int index) {
        if(index<0||index>size()) {
            throw new PosExcep("下标不合法");
        }
    }
    private LinkNode getIndexPos(int index) {
        LinkNode cur = head;
        int count = 0;
        while (count<index) {
            cur = cur.next;
            count++;
        }
        return cur;
    }
    //---------求链表大小-----------
    @Override
    public int size() {
        int count = 0;
        LinkNode cur = head;
        while (cur!=null) {
            cur = cur.next;
            count++;
        }
        return count;
    }
    //--------------清空链表-------------
    @Override
    public void clear() {
        if(head == null) {
            System.out.println("链表为空,不需要清空链表");
            return;
        }
        //此时链表不为空。
        LinkNode cur = head;
        while (cur != null) {
            LinkNode next = cur.next;
            // 断开当前节点的所有引用
            cur.prev = null;
            cur.next = null;
            cur = next;
        }
        // 最后把头尾指针置空
        head = null;
        last = null;
    }
    //-------------删除链表----------------
    @Override
    public void remove(int key) {
        //判断当前链表是否为空
        if(head == null) {
            System.out.println("链表为空");
            return;
        }
        LinkNode cur = head;
        while (cur!=null) {
            if(cur.val == key) {
                //判断是不是头节点
                if(head == cur) {
                    head = head.next;
                    if(head!=null) {
                        //防止 当前双向链表只有一个节点
                        head.prev = null;
                    }
                }else {
                    //此时就不是头节点
                    cur.prev.next = cur.next;
                    if(cur.next == null) {
                        last = last.prev;
                    }else {
                        //删除中间节点
                        cur.next.prev = cur.prev;
                    }
                }
                return;
            }
            cur = cur.next;
        }
    }
    //----------删除所有在链表中的key值------------
    @Override
    public void removeAll(int key) {
        if(head == null) {
            System.out.println("链表为空");
            return;
        }
        LinkNode cur = head;
        while (cur!=null) {
            if(cur.val == key) {
                //判断是不是头节点
                if(head == cur) {
                    head = head.next;
                    if(head!=null) {
                        //防止 当前双向链表只有一个节点
                        head.prev = null;
                    }
                }else {
                    //此时就不是头节点
                    cur.prev.next = cur.next;
                    if(cur.next == null) {
                        last = last.prev;
                    }else {
                        //删除中间节点
                        cur.next.prev = cur.prev;
                    }
                }
            }
            cur = cur.next;
        }
    }
    //-----------判断当前值是否在链表中--------
    @Override
    public boolean contains(int val) {
        if(head==null) {
            return false;
        }
        //此时链表不为空
        LinkNode cur = head;
        while (cur!=null) {
            if(cur.val == val) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
    //-----------打印链表-----------
    @Override
    public void display() {
        if(head==null) {
            System.out.println("链表为空");
            return;
        }
        //此时链表不位空
        LinkNode cur = head;
        while (cur!=null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }
}

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


相关推荐
阿维的博客日记5 小时前
Hippo4j 线程池监控平台部署手册
java·spring boot·后端
C+++Python8 小时前
详细介绍一下Java泛型的通配符
java·windows·python
JosieBook8 小时前
【数据库】时序预测能力的分级进化:TimechoAI如何让每一类用户都能精准预见未来
java·开发语言·数据库
一生了无挂9 小时前
Java处理JSON技巧教学(从基础到高阶实战全覆盖)
java·开发语言·json
李白的天不白10 小时前
使用 SmartAdmin 进行前后端开发
java·前端
swordbob10 小时前
Spring 单例 Bean 是线程安全的吗?
java·开发语言
2601_9516437710 小时前
Python第一,Java跌出前三,C语言杀回来了
java·c语言·python·编程语言排行·技术趋势
IT 行者12 小时前
GitHub Spec Kit 实战(五):/speckit.tasks 怎么拆——Spec Kit 五部曲收官
java·ai编程·claude
(Charon)13 小时前
【C++ 面试高频基础:指针、引用、const、static、new/delete 总结】
java·开发语言
Yeats_Liao13 小时前
Feed流系统设计(三):数据模型与存储设计,从表结构到Redis收件箱
java·javascript·redis