链表3(LinkedList)

news/2025/2/27 4:53:15

1、双向不带头链表的实现

1.1 节点成员和构造方法

        双向不带头链表相比于单向多了一个prev域,它能使链表获得前驱节点。

        如上图是双向不带头链表的一个节点,它可以直接找到前驱和后继节点。

        由上面的讲解可得到代码:(注意:节点的类为静态内部类

//静态内部类  
static class ListNode {
        private int val;//值域
        private ListNode prev;//前驱
        private ListNode next;//后继
        //构造方法
        public ListNode(int val) {
            this.val = val;
        }
    }

        相比于单向链表的头节点,在双向链表中,多了一个尾巴节点last,方便了尾插法。

1.2 size()、display()、contains()

链表长度、打印链表、查找链表中是否包含该元素

        这三个方法与单链表相同都是通过头节点遍历链表,这里就不做过多赘述。

        代码如下:

 //得到双链表的长度
 public int size(){
            int length = 0;
            ListNode cur = head;
            while(cur != null){
                length++;
                cur = cur.next;
            }
            return length;
        }
 // 打印链表
 public void display(){
            ListNode cur = head;
            while(cur != null){
                System.out.print(cur.val + " ");
                cur = cur.next;
            }
            System.out.println();
        }
    //查找是否包含关键字key是否在单链表当中
        public boolean contains(int key){
            ListNode cur = head;
            while(cur != null){
                if(cur.val == key){
                    return true;
                }
                cur = cur.next;
            }
            return false;
        }

1.3 addFirst()

头插法

        相比于单链表的头插法,双链表的头插法又有什么不同呢?

        如图,要将33头插入当前双链表中,在单链表中,我们只需要绑定后面的元素 ,然后再把头节点给到node,而双链表则还需要改掉原来头节点的前驱。

        由此,有代码如下:

                    node.next = head;
                    head.prev = node;
                    head = node;

        此时,观察代码我们会想:如果我的链表中没有元素,那这里的不就发生空指针异常了吗?

        因此,我们需要判断,当head==null的时候,我们把链表的头节点和尾巴节点都给到node,代码如下:

 if(head == null){
                    head = node;
                    last = node;
                }

        完整代码:

public void addFirst(int data){
                ListNode node = new ListNode(data);
                if(head == null){
                    head = node;
                    last = node;
                }else{
                    node.next = head;
                    head.prev = node;
                    head = node;
                }
        }

1.4 addLast ()

尾插法

                与头插法大同小异,只需要把原来的last的next改为node,再把当前节点的前驱改为last,再让last = node即可,如果链表中没有元素,就把头和尾巴都给到head。 

        完整代码:

   public void addLast(int data){
            ListNode node = new ListNode(data);
            if(head == null){
                head = node;
                last = node;
            }else{
                node.prev = last;
                last.next = node;
                last = node;
            }
        }

1.5 addIndex()

在下标位置插入

   在单链表中,addIndex方法需要找到下标的前一个元素。而在双链表中则可以直接找到该下标的元素(因为我们可以通过前驱来找到前一个元素)。    

          如图所示,我们需要改动4处位置,这里一定要注意这4处的顺序:建议最先改node的prev和next,然后改cur.prev.next,前面的顺序可以改变,但是cur的prev一定是最后改的。 因为是任意位置插入,在0位置我们直接调用头插法函数;在size()位置直接调用尾插法函数;下标不合法,抛异常。

          完整代码: 

//任意位置插入,第一个数据节点为0号下标
        public void addIndex(int index,int data){
            ListNode listNode = new ListNode(data);
            if(index < 0 ||index > size()){
                throw new IndexErrorException(index+"下标错误,无法插入");
            }
            if(index == 0){
                addFirst(data);
                return;
            }
            if(index == size()){
                addLast(data);
                return;
            }
            int count = 0;
            ListNode cur = head;
            while(count != index){
                cur = cur.next;
                count++;
            }
            listNode.next = cur;
            listNode.prev = cur.prev;
            cur.prev.next = listNode;
            cur.prev = listNode;
        }

1.6 remove()

删除第一次出现值为key的节点 

    例如:要删除的key为34。

        因为有了前驱和后继,我们可以直接让cur遍历到值为key节点 ,然后让cur.next.prev = cur.prev; cur.prev.next = cur.next;

         细心的同学此时会发现问题:那我如果要删除的元素是头节点或尾节点,不发生空指针异常了吗?

        是的,所以我们需要写两个if语句进行判断来单独删除头节点和尾巴节点:删除头节点head = head.next; head.prev = null;此外,还应考虑链表只有一个节点的情况:只需将last置空即可。删除尾节点:cur.next.prev = null; last = last.prev;

        代码如下

1.7 removeAllKey()

删除所有值为key的节点

        删除所有值为key的元素,只需要把remove()代码中的return删去,让cur继续走下去即可。

        代码如下:

//删除所有值为key的节点
        public void removeAllKey(int key){
            ListNode cur = head;
            while(cur != null){
                if(cur.val == key){
                    //删除头结点
                    if(cur == head){
                        head = head.next;
                        if(head != null) {
                            head.prev = null;
                        }else{//只有一个节点的情况
                            last = null;
                        }

                    }else {//删除中间和尾巴
                        //删除尾巴
                        if(cur.next == null){
                            cur.prev.next = cur.next;
                            last = last.prev;

                        }else {//删除中间
                            cur.prev.next = cur.next;
                            cur.next.prev = cur.prev;

                        }
                    }
                }
                cur = cur.next;
            }
        }

 1.8 clear()

清空链表 

         清空链表的所有节点,有两种方法:

1、暴力清空(将head和last直接置空)

public void clear(){
    head = null;
    last = null;
}

 2、将所有节点的前驱和后继置空

   public void clear(){
            ListNode cur = head;
            while(cur != null){
                ListNode curNext = cur.next;
                cur.prev = null;
                cur.next = null;
                cur = curNext;
            }
            head = null;
            last = null;
          }

2、LinkedList和ArrayList的区别

        一、从存储方面:ArrayList在物理和逻辑上都是连续的;LinkedList在物理上不连续,但在逻辑上是连续的。

        二、增删查改和访问元素方面:

        ArrayList在插入的时候需要将被删元素后面的元素后移一个单位,时间复杂度为O(n),而LinkedList只需要直接把节点插进去,时间复杂度为O(1)。此外,ArrayList空间不够需要1.5倍扩容;而链表对容量没有概念,只需要新建一个节点插入即可。

        ArrayList可以通过下标随机访问元素,时间复杂度O(1);LinkedList则只能通过cur一个一个寻找,复杂度为O(n)。

        总结:访问元素次数多的使用顺序表,增删查改次数多则使用链表

 


http://www.niftyadmin.cn/n/5869456.html

相关文章

获取GitHub的OAuth2的ClientId和ClientSecrets

获取 GitHub OAuth2 登录所需的 client-id 和 client-secret 登录 GitHub&#xff1a;使用你的 GitHub 账号登录到 GitHub。访问开发者设置&#xff1a;点击右上角的头像&#xff0c;选择 Settings&#xff0c;然后在左侧导航栏中选择 Developer settings。创建新的 OAuth 应用…

被裁20240927 --- WSL-Ubuntu20.04安装cuda、cuDNN、tensorRT

cuda、cuDNN、tensorRT的使用场景 1. CUDA&#xff08;Compute Unified Device Architecture&#xff09; 作用&#xff1a; GPU 通用计算&#xff1a;CUDA 是 NVIDIA 的并行计算平台和编程模型&#xff0c;允许开发者直接利用 GPU 的并行计算能力&#xff0c;加速通用计算任…

springcloud nacos 整合seata解决分布式事务

文章目录 nacos安装Mysql5.7安装及表初始化seata server安装下载并解压seata安装包在conf文件夹修改file.conf文件向本地数据库导入seata需要的表修改registry.conf文件将seata配置信息添加到nacos配置中心启动seata server springcloud整合seata测试流程正常下单流程扣减库存失…

学习路程四 向量数据库Milvus安装与连接

前序 在之前&#xff0c;已经简单完成了文档的加载&#xff0c;分割&#xff0c;向量化这些步骤&#xff0c;最后得到了结果。但是这些数据都是一次性的。假设一个律师所&#xff0c;有几千上万份卷宗&#xff0c;不可能每次使用都重新向量化数据吧。 所以我们需要有一个地方存…

LangChain系列:精通LangChain的合并文档链

LangChain的合并链旨在解决语言模型处理长文本时的上下文限制问题&#xff0c;包含Stuff、MapReduce、Refine和Rerank四种策略。Stuff链通过简单拼接文档块实现快速处理&#xff0c;适用于短文本但受限于模型token容量&#xff1b;MapReduce链采用分治思想&#xff0c;先独立处…

Gradio全解11——使用transformers.agents构建Gradio UI(6)

大模型WebUI:Gradio全解11——使用transformers.agents构建Gradio UI(6) 前言本篇摘要11. 使用transformers.agents构建Gradio UI11.6 通过agents构建Gradio UI11.6.1 ChatMessage数据类1. 数据结构2. 例程11.6.2 构建Gradio UI示例1. 代码及运行2. 代码解读参考文献前言 本…

【HTML— 快速入门】HTML 基础

准备工作 vscode下载 百度网盘 Subline Text 下载 Sublime Text下载 百度网盘 vscode 下载 Sublime Text 是一款轻量好用的文本编辑器&#xff0c;我们在写前端代码时&#xff0c;使用 Sublime Text 打开比使用记事本打开&#xff0c;得到的代码体验更好&#xff0c;比 vscode…

学习笔记05——HashMap实现原理及源码解析(JDK8)

HashMap实现原理及源码解析&#xff08;JDK8&#xff09; 一、核心设计思想 数组链表红黑树&#xff1a;桶数组存储Node节点&#xff0c;哈希冲突时形成链表&#xff0c;链表长度≥8且桶数量≥64时转红黑树扰动函数&#xff1a;(h key.hashCode()) ^ (h >>> 16) 消除…