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();
    }
}

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


相关推荐
薪火铺子1 小时前
SpringMVC请求处理流程源码解析(第3篇):视图渲染与异常处理
java·后端·spring
逻辑驱动的ken1 小时前
Java高频面试场景题19
java·开发语言·面试·职场和发展·求职招聘
如何原谅奋力过但无声2 小时前
【灵神高频面试题合集01-03】相向双指针、滑动窗口
数据结构·python·算法·leetcode
leoufung2 小时前
LeetCode 42:接雨水 —— 从“矩形法”到双指针的完整思考过程
java·算法·leetcode
小碗羊肉2 小时前
【MySQL | 第十一篇】InnoDB引擎
java·数据库·mysql
Dylan的码园2 小时前
Maven基础架构与整体认识
java·junit·maven
弹不出的5h3ll2 小时前
Ghost Bits:高位截断如何让 Java WAF 形同虚设
java·开发语言
庞轩px3 小时前
第七篇:注解与APT深度解析——从@Override到Lombok的底层原理
java·注解·编译·lombok
千寻girling3 小时前
五一劳动节快乐 [特殊字符][特殊字符][特殊字符]
java·c++·git·python·学习·github·php