数据结构(Java版)第九期:LinkedList与链表(四)

数据结构(Java版)第九期:LinkedList与链表(四)
专栏:数据结构(Java版)

个人主页:手握风云

目录

一、LinkedList的模拟实现

1.1. 头插法

1.2. 尾插法

1.3. 插入中间节点

1.4. 删除某个节点

1.5. 删除所有为key的元素

二、LinkedList的使用

2.1. 什么是LinkedList

2.2. LinkedList的使⽤

三、ArrayList和LinkedList的区别


一、LinkedList的模拟实现

        在前几期当中,我们实现的都是单向链表,这次我们要实现的是双向链表。双向链表每个节点包含三个域,分别是val,储存数据;prev储存上一个节点的地址;next,储存下一个节点的地址。(如下图所示)

        对于双向链表的实现,我们新建一个MyLinkedList类,新建一个接口IList,在类里面重写IList里面的方法。

public class MyLinkedList implements IList{ static class ListNode{ public int val; public ListNode prev; public ListNode head; public ListNode(int val) { this.val = val; } } public ListNode head;//链表的头 public ListNode last;//链表的尾 @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 display() { } @Override public void clear() { } } 
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 display(); public void clear(); } 

1.1. 头插法

        对于实现尾插的方法其实很简单,把新的节点的next存储下一个节点的地址,同时旧的头节点的prev也要存储新的头节点的地址。

@Override 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; } } @Override public void display() { ListNode cur = head; while(cur != null){ System.out.print(cur.val+" "); cur = cur.next; } }
public class Main { public static void main(String[] args) { MyLinkedList myLinkedList = new MyLinkedList(); myLinkedList.addFirst(50); myLinkedList.addFirst(40); myLinkedList.addFirst(30); myLinkedList.addFirst(20); myLinkedList.addFirst(10); myLinkedList.addFirst(5); myLinkedList.display(); } }

    我们顺带也可以完成对链表长度和是否含有某个元素的实现

@Override public int size() { int count = 0; ListNode cur = head; while(cur != null){ count++; cur = cur.next; } return count; } public class Main { public static void main(String[] args) { MyLinkedList myLinkedList = new MyLinkedList(); myLinkedList.addLast(10); myLinkedList.addLast(20); myLinkedList.addLast(30); myLinkedList.addLast(40); myLinkedList.addLast(50); myLinkedList.addLast(60); System.out.println(myLinkedList.size()); } }
@Override public boolean contains(int key) { ListNode cur = head; while(cur != null){ if(cur.val == key){ return true; } cur = cur.next; } return false; }

1.2. 尾插法

      尾插法与头插法极其类似,这里不再过多叙述。

@Override public void addLast(int data) { ListNode node = new ListNode(data); if(head == null){ head = node; last = node; }else{ last.next = node; node.prev = last; last = node; } }
public class Main { public static void main(String[] args) { MyLinkedList myLinkedList = new MyLinkedList(); myLinkedList.addLast(10); myLinkedList.addLast(20); myLinkedList.addLast(30); myLinkedList.addLast(40); myLinkedList.addLast(50); myLinkedList.addLast(60); myLinkedList.display(); } } 

1.3. 插入中间节点

 

       如图所示,我们需要用到4个引用,大致过程可以通过4行代码来实现。先绑定后面,要注意修改每一个引用的时候,不能因为修改了前面而影响了后面。

node.next = cur; cur.prev.next = node; node.prev = cur.prev; cur.prev = node;
@Override public void addIndex(int index, int data) { int len = size(); if(index<0 || index>len){ throw new RuntimeException("双向链表位置不合法:"+index); } if(index == 0){ addFirst(data); return; } if(index == len){ addLast(data); } ListNode node = new ListNode(data); ListNode cur = findIndex(index); node.next = cur; cur.prev.next = node; node.prev = cur.prev; cur.prev = node; } private ListNode findIndex(int index){ ListNode cur = head; while(index != 0){ cur = cur.next; index--; } return cur; } 

1.4. 删除某个节点

    如果说,我们删除的只是中间的某个节点,我们只需要两行代码就能实现。

cur.prev.next = cur.next; cur.next.prev = cur.prev;

       但是我们要删除的是头节点或者尾结点呢?我们可以先让head向前走一步,再把head.prev置为空,同理last节点也可以。

@Override public void remove(int key) { ListNode cur = head; while(cur != null){ if(cur.val == key){ //删除头节点 if(cur == head){ head = head.next; head.prev = null; }else{ //删除尾结点 if(cur == last){ last = last.prev; last.next = null; }else{ //删除中间节点 cur.prev.next = cur.next; cur.next.prev = cur.prev; } } return; }else{ cur = cur.next; } } }

        这么看我们的代码好像没问题,但我们需要考虑一个问题,如果说链表只有一个节点呢?就会抛出下图这样一个异常,我们只需要把last也手动置为空。

完整代码:

@Override public void remove(int key) { ListNode cur = head; while(cur != null){ if(cur.val == key){ //删除头节点 if(cur == head){ head = head.next; if(head == null){ last = null; return; } head.prev = null; }else{ //删除尾结点 if(cur == last){ last = last.prev; last.next = null; }else{ //删除中间节点 cur.prev.next = cur.next; cur.next.prev = cur.prev; } } return; }else{ cur = cur.next; } } }

1.5. 删除所有为key的元素

        我们只需要对上一个方法进行一点改动就可以实现。我们让cur只遍历一遍链表,每次cur走完if语句之后直接去指向下一个节点。

@Override public void removeAllKey(int key) { ListNode cur = head; while(cur != null){ if(cur.val == key){ //删除头节点 if(cur == head){ head = head.next; if(head == null){ last = null; return; } head.prev = null; }else{ //删除尾结点 if(cur == last){ last = last.prev; last.next = null; }else{ //删除中间节点 cur.prev.next = cur.next; cur.next.prev = cur.prev; } } } cur = cur.next; } }

二、LinkedList的使用

2.1. 什么是LinkedList

     从上面的图中可以看到LinkedList实现了众多的接口来帮助我们实现各种各样的功能,这里我们主要看List的接口。我们看一下LinkedList里面的源码

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable

2.2. LinkedList的使⽤

import java.util.LinkedList; public class Main { public static void main(String[] args) { LinkedList<Integer> linkedList = new LinkedList<>(); linkedList.addFirst(10); linkedList.addLast(20); System.out.println(linkedList); } }

       我们通过LinkedList创建对象的时候,就可以直接调用以上方法。我们看一下addFirst和addLast的源码。

 public void addFirst(E e) { linkFirst(e); } 
 public void add(E e) { checkForComodification(); lastReturned = null; if (next == null) linkLast(e); else linkBefore(e, next); nextIndex++; expectedModCount++; }

    我们可以看到,代码的思路与我们上面自己实现的基本一致。我们同样可以初始化一个ArrayList。下面是一些常用方法:

import java.util.LinkedList; public class Main { public static void main(String[] args) { LinkedList<Integer> list1 = new LinkedList<>(); list1.add(1);//表示尾插 list1.add(2); list1.add(3); list1.add(4); list1.add(5); list1.add(6); list1.add(3,100);//在下标为3的位置插入100 System.out.println(list1); LinkedList<Integer> list2 = new LinkedList<>(); list2.add(11); list2.add(22); list2.add(33); list1.addAll(2,list2); System.out.println(list1); list1.remove(2);//删除下标为2的元素 list1.removeFirst();//删除头元素 list1.removeLast();//删除尾元素 System.out.println(list1); System.out.println(list1.get(2));//获取下标为2的元素 list1.set(0,20); System.out.println(list1);//将下标为0的元素设置为20 } }

三、ArrayList和LinkedList的区别

不同点ArrayListLinkedList
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持O(n)
头插需要搬移元素,效率低O(n)只需要改变指向,时间复杂度O(1)
插入空间不够时需要扩容没有容量的概念

Read more

安装openclaw时出现npm error code ENOENT npm error syscall spawn git报错的解决方案

安装openclaw时出现npm error code ENOENT npm error syscall spawn git报错的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为ZEEKLOG博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的知识进行总结与归纳,不仅形成深入且独到的理解,而且能够帮助新手快速入门。 本文主要介绍了安装openclaw时出现npm error code ENOENT npm error syscall spawn git报错的解决方案,希望能对使用openclaw的同学们有所帮助。 文章目录 * 1. 问题描述 * 2. 解决方案 1. 问题描述 今天在使用命令安装openclaw时,却出现了npm error code ENOENT和npm error syscall spawn git的错误提示,具体报错信息如下图所示: 在经过了亲身的实践后,终于找到了解决问题的方案,最终将逐步的操作过程总结如下。希望能对遇到同样bug的同学们有所帮助。

By Ne0inhk
最新版 GLM-5 全栈实战全教程:从本地开源部署到 API 接入(多 Agent 架构 + 全栈编程 + 就业级项目实战)

最新版 GLM-5 全栈实战全教程:从本地开源部署到 API 接入(多 Agent 架构 + 全栈编程 + 就业级项目实战)

一、背景与技术概述 随着开源大模型技术的快速迭代,GLM-5 系列凭借优秀的指令遵循能力、长上下文支持、轻量化部署适配性与商用友好的开源协议,成为企业级AI落地与个人开发者技术进阶的核心选型之一。 本文以问题驱动为核心,完整覆盖从本地开源部署到工程化API封装、多Agent架构设计、全栈项目实战的全流程,解决开发者在大模型落地过程中面临的部署门槛高、工程化能力不足、Agent架构落地难、全栈项目缺乏可复用方案等核心痛点。本文所有实操步骤均经过生产环境验证,代码可直接复用,适配就业级项目的技术要求与企业落地标准。 1.1 GLM-5 核心技术特性 * 开源协议:Apache 2.0 协议,支持商用二次开发,无额外授权门槛 * 核心能力:支持128K超长上下文窗口,原生支持函数调用、多模态理解、结构化输出,指令遵循准确率较前代提升42% * 部署适配:原生支持FP8/INT4/AWQ/GPTQ多精度量化,最低可在16G显存环境完成流畅推理,适配消费级显卡与企业级GPU集群 * 性能优化:基于稀疏注意力架构与PagedAttention机制,推理吞吐量较同参数量模型提升3倍,

By Ne0inhk
爆肝 2 天,用 GLM5 开发了 OpenClaw 接入微信 bot,已开源!

爆肝 2 天,用 GLM5 开发了 OpenClaw 接入微信 bot,已开源!

这是苍何的第 493 篇原创! 大家好,我是苍何。 OpenClaw,这个 GitHub 上 18 万 Star 的怪物级开源项目,你们应该都听过了吧? 飞书能接、钉钉能接、企业微信能接、QQ 能接、Discord 能接…… 但偏偏最多人用的「微信个人号」,它不支持。 我翻遍了 GitHub、掘金、知乎,找到的方案要么是企业微信绕一圈,要么是用微信 Web 协议搞,动不动就封号。 说实话,这谁顶得住? 天天在微信上跟朋友聊天、在群里吹水,结果想接个 OpenClaw 都这么费劲? 麻了。 于是我决定自己干。 「爆肝 2 天,我把 OpenClaw 接入了微信个人号,并且已经开源了。」 地址:

By Ne0inhk
【AI绘画】Midjourney进阶:色相详解

【AI绘画】Midjourney进阶:色相详解

博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳]本文专栏: AI绘画 | Midjourney 文章目录 * 💯前言 * 💯Midjourney中的色彩控制 * 为什么要控制色彩? * 为什么要在Midjourney中控制色彩? * 💯色相 * 红 * 橙 * 黄 * 绿 * 蓝 * 紫 * 黑与白 * 💯小结 💯前言 在设计领域中,色相作为色彩的重要维度,直接决定了作品的视觉基调与情感表达。通过对色相的深入理解与灵活运用,设计师可以在作品中精准传递信息,激发观众的情感共鸣。Midjourney 作为一款强大的AI绘画工具,为设计师提供了高效探索色相表现的创作平台,使复杂的色彩控制变得直观且富有创意。 本篇文章将以色相为核心,从色彩心理学与实际应用出发,结合 Midjourney 的提示词设置,详细解析不同色相在设计中的作用与特点。无论是自然主题的绿、蓝,还是富有情感张力的红、紫,每一种色相都在设计中扮演着不可替代的角色。 Midjourney官方使用手册 💯Midjourney中的色彩控制 在 Mi

By Ne0inhk