• Java(十八)---单链表



    前言

    在上一篇文章中我们学习了ArrayList顺序表的相关操作和模拟实现,ArrayList顺序表的底层使用数组进行维护的,由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java集合中又引入了LinkedList,即链表结构。


    1.链表的概念及结构

    链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。
    在这里插入图片描述

    由上面的图我们可以得知:每种链表都是由一个一个节点组成的,主要分为三类 有无头节点,是否循环以及 是单向的还是双向的,一共8种类型。
    其中我们只学习2类,第一类是无头非循环单向链表。
    第二种是无头非循环双向链表(Java中自带的链表)
    下来我们来模拟一下单链表的相关操作。

    2.单链表的创建

    功能实现:

    //无头单向非循环链表实现
    //头插法
    public void addFirst(int data);
    //尾插法
    public void addLast(int data);
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data);
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key);
    //删除第一次出现关键字为key的节点
    public void remove(int key);
    //删除所有值为key的节点
    public void removeAllKey(int key);
    //得到单链表的长度
    public int size();
    public void clear();
    public void display();

    我们把上述功能放在IList接口里面,即使是以后学到的LinkList(双向链表)也要实现这些功能,这样的话,只需要使用重写接口即可,不需要在将这些方法放到类中实现。
    Llist接口

    public interface IList {
        //无头单向非循环链表实现
        //头插法
        public void addFirst(int data);
        //尾插法
        public void addLast(int data);
        //任意位置插入,第一个数据节点为0号下标
        public void addIndex(int index,int data);
        //查找是否包含关键字key是否在单链表当中
        public boolean contains(int key);
        //删除第一次出现关键字为key的节点
        public void remove(int key);
        //删除所有值为key的节点
        public void removeAllKey(int key);
        //得到单链表的长度
        public int size();
        public void clear();
        public void display();
    }
    

    节点的创建:

    public class MySingleList {
        static class ListNode{
            public int val;
            public ListNode next;
    
            public ListNode(int val) {
                this.val = val;
            }
        }   
    }
    

    然后连接接口

    public class MySingleList implements IList {
        static class ListNode{
            public int val;
            public ListNode next;
    
            public ListNode(int val) {
                this.val = val;
            }
        }
        public ListNode head;
        @Override
        public void addFirst(int data) {
    
        }
    
        @Override
        public void addLast(int data) {
    
        }
    
        @Override
        public void addIndex(int index, int data) {
    
        }
    
        @Override
        public boolean contains(int key) {
            return false;
        }
    
        @Override
        public void remove(int key) {
    
        }
    
        @Override
        public void removeAllKey(int key) {
    
        }
    
        @Override
        public int size() {
            return 0;
        }
    
        @Override
        public void clear() {
    
        }
    
        @Override
        public void display() {
    
        }
    }
    

    3.功能的实现

    3.1.创建链表(create)(需要自己创建)

    第一次看这个方法,可能比较糙,先这样写,等会写到3种add方法的时候,再用高级的方法。为等会测试display,size()等方法,做铺垫

     public void create(){
            ListNode node = new ListNode(1);
            ListNode node2 = new ListNode(2);
            ListNode node3 = new ListNode(3);
            ListNode node4 = new ListNode(4);
            head = node;
            node.next = node2;
            node2.next = node3;
            node3.next = node4;
        }
    

    3.2.显示链表(display)

    显示链表(display)的逻辑和获取节点个数(size() )以及 是否包含指定元素的节点(contains)一样。
    在进行循环链表的时候,我们一般会在创建一个节点(cur)来代替head,让cur去循环链表而不是让head循环链表。
    在这里插入图片描述

    public void display() {
            ListNode cur = head;
            while (cur != null){
                System.out.print(cur.val+" ");
                cur = cur.next;
            }
            System.out.println();
        }
    

    3.3.获取链表的个数( size() )

    跟上面的逻辑如出一辙

     public int size() {
            ListNode cur = head;
            int count = 0;
            while (cur != null){
                count++;
                cur = cur.next;
            }
            return count;
        }
    

    3.4.是否包含指定元素(contains)

    public boolean contains(int key) {
            ListNode cur = head;
            while (cur != null){
                if(cur.val == key){
                    return true;
                }
                cur = cur.next;
            }
            return false;       
        }
    

    3.5.头插法(addFirst)

    在这里插入图片描述
    在进行插入和删除的时候,一定要注意只有一个head节点的情况,可能会进行分类讨论。

    public void addFirst(int data) {
            ListNode node = new ListNode(data);
            node.next = head;
            head = node;
        }
    

    3.6.尾插法(addLast)

    在这里插入图片描述

     public void addLast(int data) {
            ListNode node = new ListNode(data);
            if(head == null){
                head = node;
                return;
            }
            ListNode cur = head;
            while (cur.next != null){
                cur = cur.next;
            }
            cur.next = node;
        }
    

    3.7.在指定位置进行插入(addIndex)

    在这里插入图片描述

        public void addIndex(int index, int data) {
            int len = size();
            if (index < 0 || index > len){
                throw new CheckPos("位置不合法...");
            }
            if (index == 0){
                addFirst(data);
                return;
            }
            if (index == len){
                addLast(data);
                return ;
            }
            ListNode node = new ListNode(data);
            ListNode cur = head;
    
            while (index - 1 != 0){
                cur = cur.next;
                index-- ;
            }
            node.next = cur.next;
            cur.next = node;
        }
    

    为了将不合规的位置特殊标出一下,使用了异常。

    public class CheckPos extends RuntimeException{
        public CheckPos(){
            super();
        }
        public CheckPos(String s){
            super(s);
        }
    
    }
    

    3.8.删除出现在第一次的指定元素的节点(remove)

    @Override
        public void remove(int key) {
            if(head == null) {
                return;
            }
            if(head.val == key) {
                head = head.next;
                return;
            }
            ListNode cur = NodeOfKey(key);
            ListNode del = cur.next;
            cur.next = del.next;
        }
    

    在这里插入图片描述

    3.9.删除全部出现指定元素的节点(removeAllKey)

    要求只遍历1遍,就可以清除指定元素的节点。
    可以使用两个变量进行操作 (prev.cur)
    在这里插入图片描述

    private ListNode NodeOfKey(int key){
            ListNode cur = head;
            while (cur != null){
                if (cur.next.val == key){
                    return cur;
                }
                cur = cur.next;
            }
            return null;
        }
    
    public void removeAllKey(int key) {
            if(head == null){
                return;
            }
            ListNode prev = head;
            ListNode cur = head.next;
            while (cur != null){
                if (cur.val == key){
                    prev.next = cur.next;
                    cur = cur.next;
                }else{
                    prev = cur;
                    cur = cur.next;
                }
            }
            if (head.val == key){
                head = head.next;
            }
        }
    

    3.10.清空链表

    在这里插入图片描述

    @Override
        public void clear() {
            ListNode cur = head;
            while(cur != null){
                ListNode curN = null;
                cur = null;
                cur = curN;
            }
            head = null;
        }
    

    下一篇我们来讲几道关于单链表的题。

  • 相关阅读:
    198. 打家劫舍
    thymeleaf中的日期格式转化
    Python装饰器的四种定义形式
    Oracle常用函数大全
    渗透测试学习day3
    辅助解决小白遇到的电脑各种问题
    uniapp物理键/右滑多次退出应用,再次进入显示白屏的问题
    peft模型微调--Prompt Tuning
    贝塞尔函数
    FPGA Quartus IP核 打开使用
  • 原文地址:https://blog.csdn.net/xxhh156/article/details/140345013