从0到1手撕双向链表,再到LinkedList源码拆解,这篇数据结构干货让你直接逆袭成大佬

从0到1手撕双向链表,再到LinkedList源码拆解,这篇数据结构干货让你直接逆袭成大佬
在这里插入图片描述
🎁个人主页:User_芊芊君子
🎉欢迎大家点赞👍评论📝收藏⭐文章
🔍系列专栏:Java.数据结构
在这里插入图片描述


在这里插入图片描述


【前言】

在Java开发中, LinkedList 是集合框架里的重要成员,其底层基于双向链表实现。对于开发者来说,搞清楚双向链表的工作机制,能让我们更精准地运用 LinkedList ,还能提升对数据结构选型的把控力。本文先带你吃透双向链表的模拟实现,涵盖节点设计、各类插入删除操作;再深入 LinkedList 的源码,解析它的构造、方法和遍历逻辑;最后对比它与 ArrayList 的差异,助力你在实际开发中做出更优的技术选择。

文章目录:

一、双向链表

双向链表与单向链表相比,多了一个前驱prev,但解决了单向链表很多缺点:单向链表的每个节点仅存储后继节点地址,这导致其在实际应用中存在明显局限:

遍历方向单一:只能从表头(head)向表尾(tail)遍历,无法反向查找。删除操作低效:若要删除指定节点,需先从头遍历找到其前驱节点,时间复杂度为O(n)。表尾操作不便:无法直接获取表尾节点的前驱,若需在表尾插入后修改前驱指针,需重新遍历。

双向链表通过在节点中增加前驱节点地址域,解决了以上问题,so在实际开发中,使用更多的是双向链表


在这里插入图片描述

二、双向链表的模拟实现

1.节点类

val数据,next后继,prev前驱,双向链表的前驱节点,可以找到前一个节点,为了方便从后往前遍历,我们使用last标记最后一个节点

staticclassListNode{publicint val;publicListNode next;publicListNode prev;publicListNode(int val){this.val = val;}}publicListNode head;publicListNode last;

2.头插法

先创建一个新节点node,如果链表为空,head和last都指向新节点,当链表不为空时,让新节点的next指向头节点(先绑定后面),然后将头节点的前驱prev赋值为新节点的地址,最后让head指向新的节点

在这里插入图片描述
publicvoidaddFirst(int data){ListNode node =newListNode(data);if(head ==null){ head = node; last = node;}else{ node.next = head; head.prev = node; head = node;}}

3.尾插法

跟头插法一样,创建一个节点,判断链表是否为空,为空就让head和last都指向新节点,当链表不为空时,让最后一个节点的next指向新的节点(先绑定),然后让新节点的前驱指向前一个节点的地址,最后让last指向新节点

在这里插入图片描述
publicvoidaddLast(int data){ListNode node =newListNode(data);if(head ==null){ head = node; last = node;}else{ last.next = node; node.prev = last; last = node;}}

4.指定位置插入

双向链表可以找到前一个节点,所以可以让cur直接走到想要插入的位置,先判断位置是否合法,然后开始插入,先绑定后面让新节点的next指向cur节点的地址,然后让前一个节点的next指向新的节点的地址,在将新节点的前驱改为cur的前驱,cur的前驱改为新节点

在这里插入图片描述
publicvoidaddIndex(int index,int data){//检查index的合法性check(index);//特殊位置处理if(index ==0){addFirst(data);return;}if(index ==size()){addLast(data);return;}//指定插入ListNode cur =searchIndex(index);ListNode node =newListNode(data); node.next = cur; cur.prev.next = node; node.prev = cur.prev; cur.prev = node;}//寻找位置privateListNodesearchIndex(int index){ListNode cur = head;while(index!=0){ cur = cur.next; index--;}return cur;}//自定义异常privatevoidcheck(int index){if(index <0|| index >size()){thrownewcheck("index位置不合法"+ index);}}

5.删除值为key的节点

先遍历,找到所要删除的那个节点,当cur走到要删除的节点时,常规操作:cur.next.prev = cur.prev; cur.prev.next = cur.next;但还有其他情况:比如要删除的是头节点或者尾节点,只要一个节点的情况,具体操作看下面的代码详解

在这里插入图片描述
publicvoidremove(int key){ListNode cur = head;while(cur !=null){if(cur.val == key){//开始删除if(cur == head){//删除的时头节点 head = head.next;//如果只有一个节点,head为空,它的前驱就会空指针异常,所以判断一下if(head!=null){ head.prev =null;}}else{//不是头节点if(cur == last){//删除的是尾节点 cur.prev.next = cur.next; last = last.prev;}else{//正常情况:中间节点 cur.next.prev = cur.prev; cur.prev.next = cur.next;}}return;} cur = cur.next;}}

6.删除所有值为key的节点

只要将上面的代码中return去掉,就能删除所有值为key的节点,删完一个后不返回,继续遍历,直到全部删除

publicvoidremoveAllKey(int key){ListNode cur = head;while(cur !=null){if(cur.val == key){//开始删除if(cur == head){//删除的时头节点 head = head.next;//如果只有一个节点,head为空,它的前驱就会空指针异常,所以判断一下if(head!=null){ head.prev =null;}}else{//不是头节点if(cur == last){//删除的是尾节点 cur.prev.next = cur.next; last = last.prev;}else{//正常情况:中间节点 cur.next.prev = cur.prev; cur.prev.next = cur.next;}}} cur = cur.next;}}

7.清空链表

【方法一:】直接将头和尾置空

publicvoidclear(){ head = last =null;}

【方法二:】将一个一个的节点置空

publicvoidclear(){ListNode cur = head;while(cur !=null){ListNode curN = cur.next; cur.prev =null; cur.next =null; cur =curN;} head =null; last =null;}

二、LinkedList

1.LinkdeList的概念

LinkedList使用双向链表结构,实现了List接口。在任意位置插入和删除元素时效率较高,时间复杂度为O(1)。

2.构造方法

方法说明表格

方法签名描述
LinkedList()无参构造方法,创建一个空链表。
public LinkedList(Collection<? extends E> c)使用其他集合容器中的元素构造List
publicstaticvoidmain(String[] args){LinkedList<Integer> list =newLinkedList<>();List<Integer> list1 =newLinkedList<>();List<Integer> list2 =newArrayList<>(); list.add(1);//默认尾插 list.add(11); list.add(12); list.add(13);System.out.println(list);}
在这里插入图片描述

3.LinkedList常用方法

方法解释
boolean add(E e)在列表尾部插入元素e
void add(int index, E element)将元素e插入到指定index位置
boolean addAll(Collection<? extends E> c)将集合c中所有元素插入到列表尾部
E remove(int index)删除并返回index位置的元素
boolean remove(Object o)删除列表中首次遇到的指定对象o
E get(int index)获取index位置的元素
E set(int index, E element)index位置的元素替换为element并返回旧值
void clear()清空列表中的所有元素
boolean contains(Object o)判断列表中是否包含对象o
int indexOf(Object o)返回第一个o对象的下标,未找到返回-1
int lastIndexOf(Object o)返回最后一个o对象的下标,未找到返回-1
List<E> subList(int fromIndex, int toIndex)截取从fromIndextoIndex的子列表

4.LinkedList遍历

4.1 直接打印

System.out.println(list);

4.2 for-each遍历

for(Integer x : list){System.out.println(x+" ");}

4.3 迭代器遍历

//迭代器遍历Iterator<Integer> it = list.iterator();while(it.hasNext()){System.out.println(it.next()+" ");}//迭代器遍历:专门遍历ListListIterator<Integer> listIterator = list.listIterator();//可以给参数,从index开始打印while(listIterator.hasNext()){System.out.println(listIterator.next()+" ");}//反向迭代器ListIterator<Integer> listIterator1 = list.listIterator();//可以给参数,从index开始打印while(listIterator.hasPrevious()){System.out.println(listIterator.next()+" ");}
在这里插入图片描述

5.ArrayList和LinkList的区别

不同点ArrayListLinkedList
存储空间物理上连续逻辑上连续,物理上不连续
随机访问支持O(1)时间复杂度不支持,需遍历O(N)时间复杂度
头插操作需搬移元素,效率低O(N)修改引用指向,效率高O(1)
插入扩容空间不足时需扩容无容量概念,动态增加节点
应用场景元素高效存储+频繁随机访问频繁在任意位置插入/删除

三、总结

本文围绕双向链表和LinkedList展开了系统讲解。

双向链表凭借“每个节点保存前驱和后继引用”的结构,在插入、删除操作上有着O(1)的时间复杂度优势。LinkedList 作为双向链表的Java实现,不仅提供了如 addFirst 、 removeLast 等丰富方法,支持迭代器、增强for等多种遍历方式,还能充当队列、栈使用。和 ArrayList 相比, LinkedList 适合频繁进行插入删除的场景, ArrayList 则更适合元素存储和随机访问的场景。掌握这些知识,能让你在面对不同业务场景时,合理选择数据结构,写出高效且易维护的代码。

Read more

“现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

“现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

大模型仍未对上商业的齿轮? 编译 | 王启隆 来源 | youtu.be/aWqfH0aSGKI 出品丨AI 科技大本营(ID:rgznai100) 现在的硅谷,空气里都飘着一股“再不上车就晚了”的焦躁感。 最近 OpenClaw 风头正旺,强势登顶 GitHub,终结了 React 神话,许多人更是觉得“AI 自己干活赚钱”的日子就在明天了。 特别是在斯坦福商学院(GSB)这种地方,台下坐着的都是成天琢磨怎么用下一个技术风口搞个独角兽出来的狠人。 微软的首席科学官(CSO)Eric Horvitz 被请到了这个几乎全美最想用 AI 变现的礼堂里。作为从上世纪 80 年代就开始搞 AI 的绝对老炮、也是微软技术底座的“扫地僧”,这位老哥并没有顺着台下的胃口,去吹捧下个月大模型又要颠覆什么行业,而是兜头给大家浇了一盆带点学术味的冷水。 他讲了一个挺有画面感的比喻:大家都在聊

By Ne0inhk
诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

当宇宙级的“嘴炮”遇到降维打击。 编译 | 王启隆 来源 | youtu.be/l6ZcFa8pybE 出品丨AI 科技大本营(ID:rgznai100) 打开最新一期知名播客 StarTalk 的 YouTube 评论区,最高赞的一条留言是这样写的: “我长这么大,第一次看到尼尔·德葛司·泰森(Neil deGrasse Tyson)在一档节目里几乎全程闭嘴,像个手足无措的小学生一样乖乖听讲。” 作为全美最知名的天体物理学家,泰森平时的画风是充满激情、喋喋不休、用宇宙的宏大来震撼嘉宾。但这一次,坐在他对面的那位满头银发、带着温和英音的英国老人,仅仅用最平淡的语气,就让整个演播室陷入了数次令人窒息的沉默。 这位老人是 Geoffrey Hinton。深度学习三巨头之一,2024 年诺贝尔物理学奖得主,被公认为“AI 教父”。 对经常阅读 Hinton 演讲的我来说,这也是比较新奇的一幕—

By Ne0inhk
Godot被AI代码“围攻”!维护者崩溃发声:“不知道还能坚持多久”

Godot被AI代码“围攻”!维护者崩溃发声:“不知道还能坚持多久”

整理 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) 当大模型能在几秒钟内生成一段“看起来像那么回事”的补丁时,开源社区却开始付出另一种代价。 最近,开源游戏引擎 Godot 的核心维护团队公开吐槽:他们正被大量“AI 生成的低质量代码”淹没。那些代码往往结构完整、注释齐全、描述洋洋洒洒,但真正的问题是——提交者可能并不理解自己交上来的内容。 这件事,并不是简单的“有人偷懒用 AI 写代码”。它正在触及开源协作最核心的东西:信任。 一场悄无声息的“AI 洪水” 事情的导火索来自一条 Bluesky 讨论帖。 Godot 主要维护者之一、同时也是 Godot 商业支持公司 W4 Games 联合创始人的 Rémi Verschelde 表示,所谓的“AI slop”

By Ne0inhk
48小时“烧光”56万!三人创业团队濒临破产,仅因Gemini API密钥被盗:“AI账单远超我们的银行余额”

48小时“烧光”56万!三人创业团队濒临破产,仅因Gemini API密钥被盗:“AI账单远超我们的银行余额”

整理 | 苏宓 出品 | ZEEKLOG(ID:ZEEKLOGnews) 「仅过了 48 小时,一笔 8.2 万美元的天价费用凭空出现,较这家小型初创公司的正常月费暴涨近 46000%。」 这不是假设的虚幻故事,而是一家墨西哥初创公司正在经历的真实危机。 近日,一位名为 RatonVaquero 的开发者在 Reddit 发帖求助称,由于他的 Gemini API 密钥被盗用,原本每月仅约 180 美元(约 1242 元)的费用,在短短 48 小时内暴涨到 82,314.44 美元(约 56.8 万元)。对于这家只有三名开发者的小型创业团队来说,这笔突如其来的账单,几乎等同于灭顶之灾。 “我现在整个人都处在震惊和恐慌之中。”RatonVaquero

By Ne0inhk