篮球哥温馨提示:编程的同时不要忘记锻炼哦!

圆圆的脑袋,大大耳朵,天天敲代码,找找找bug


目录

1、什么是双向链表

2、实现一个双向链表

2.1 实现前的约定

2.2 addFirst 方法

2.3 addLast 方法

2.4 addIndex 方法

2.5 contains 方法

2.6 removeAllKey 方法

2.7 clear 方法

2.8 size 方法

3、LinkedList 的学习

3.1 认识下 LinkedList

​3.2 LinkedList 的构造方法

3.3 LinkedList 的遍历

4、ArrayList 和 LinkedList 的区别


1、什么是双向链表

上期我们实现了一下单链表,在Java(1.8)中,链表为 LinkedList,而底层是一个双向链表,跟 ArrayList 一样,LinkedList 也实现了 List 接口,这里我们画一个图,让大家简单见识下双向链表:

如图我们可以看出,双向链表最少有三个域,分别是数据域和两个指针域,分别指向节点的前驱和后继,第一个节点没有前驱,最后一个节点没有后继。


2、实现一个双向链表

2.1 实现前的约定

链表的每个元素是一个节点,我们仍然采用内部类的方式,既然是双向的,那么我们还需要在外部定义一个head和last,分别为头节点和尾节点的引用。

public class MyLinkedList {private class ListNode {private int val; //数据域private ListNode prev; //前指针域private ListNode next; //后指针域private ListNode(int val) {this.val = val;}}private ListNode head; //头节点引用private ListNode last; //尾节点引用private int size; //链表有效数据个数
}

同时我们还要实现以下几个方法:

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

2.2 addFirst 方法

//头插法
public void addFirst(int data) {ListNode newNode = new ListNode(data);// 链表为空的情况if (this.head == null) {this.head = newNode;this.last = newNode;this.size++;return;}newNode.next = this.head; //新节点的后一个为头节点this.head.prev = newNode; //头节点的前一个为新节点this.head = newNode; //新节点成为新的头节点this.size++;
}

与单链表不同,由于双向链表有头尾节点引用,所以这里我们要在第一次插入元素的时候进行特殊处理,当第一次插入元素我们需要将头尾节点的引用都指向这个节点,后续插入只需要改变头节点的引用即可,最后插入完成别忘记链表有效节点个数自增1哦!

2.3 addLast 方法

//尾插法
public void addLast(int data) {ListNode newNode = new ListNode(data);// 链表为空的情况if (this.head == null) {this.head = newNode;this.last = newNode;this.size++;return;}newNode.prev = this.last; //新节点的前一个为尾节点this.last.next = newNode; //尾节点的后一个为新节点this.last = newNode; //新节点成为新的尾节点this.size++;
}

与头插法相差不多,无非就是需要修改尾节点的引用,以及注意新节点的指针域指向问题,这里小伙伴们可以结合我的代码注释,尝试去理解,配合画图,相信你就能掌握好头插法和尾插法了!

2.4 addIndex 方法

//任意位置插入,第一个数据节点为0号下标
public boolean addIndex(int index,int data) {// 判断index下标的合法性if (index < 0 || index > this.size) {return false;}// index为0表示头插if (index == 0) {addFirst(data);return true;}// index为size长度表示尾插if (index == this.size) {addLast(data);return true;}//其他情况为中间插入ListNode newNode = new ListNode(data);ListNode cur = this.head;while (index != 0) {cur = cur.next;index--;}newNode.prev = cur.prev; //新节点的前一个为cur的前一个newNode.next = cur; //新节点的后一个为curcur.prev.next = newNode; //cur的前节点的后一个为新节点cur.prev = newNode; //cur的前节点为新节点this.size++;return true;
}

对于在指定位置插入节点来说,如果给定的 index 位置大于我们的有效节点个数呢?也就是说假设我链表只有 5 个节点,你要在 8 位置插入元素显然是不合法的,其次,如果要在负数的位置插入那更不合法,所以我们要对 index 做判断,往后走我们还可以考虑两个点,如果index == 0 或者 index == size(),那么也就是对应着我们的头插和尾插,那么我们直接调用前面写的头插尾插方法即可,代码接着往后走,剩下的就是中间插入节点的情况了,逻辑很简单,首先要找到 index 对应的节点,接着改变相关节点指针域的指向即可,这里可以结合着代码以及注释,下来画图进行分析。

2.5 contains 方法

//查找是否包含关键字key是否在双链表当中
public boolean contains(int key) {ListNode cur = this.head;while (cur != null) {if (cur.val == key) {return true;}cur = cur.next;}return false;
}

这个方法跟我们之前写单链表的时候相差无几,相信有了前面单链表的基础,这个简直是信手拈来了吧,无非就是遍历这个链表,只要 cur 没有遍历到 null,也就是没有到最后一个节点的 next 位置,我们就遍历找有没有 key,找到了返回 true 找不到返回 false 咯!

2.6 removeAllKey 方法

//删除所有值为key的节点
public void removeAllKey(int key) {ListNode cur = this.head;while (cur != null) {if (cur.val == key) {//如果被删除cur是头节点if (cur == this.head) {//只有一个节点的情况if (this.head.next == null) {this.head = null;this.last = null;} else {cur.next.prev = null; //cur的后节点的前节点指针域改为nullthis.head = cur.next; //头节点变成cur的下一个节点}} else {//如果被删除cur是尾节点if (cur == this.last) {this.last = cur.prev; //尾节点变成cur的前节点} else {cur.next.prev = cur.prev; //cur的后节点的前节点指针域改为cur的前节点}cur.prev.next = cur.next; //cur的前节点的后节点指针域改为cur的后节点}this.size--;}cur = cur.next;}
}

要删除所有值为 key 的节点,这道题思想不难,还是遍历链表嘛,如果值一样,修改相关节点引用即可,但是问题来了,删除头节点和尾节点,和删除中间节点的修改指向逻辑可不一样,所以我们要分别处理,分开处理这三种情况之后,如果只有一个节点的情况呢?也得处理,于是就有了我们上面的代码,当然可以有很多种写法,你只要把各种情况捋清楚了就好,至于修改指针域指向的逻辑画画图就能理解了!

2.7 clear 方法

public void clear() {// 遍历链表ListNode cur = this.head;while (cur != null) {ListNode curNext = cur.next;cur.prev = null;cur.next = null;cur = curNext;}this.head = null;this.last = null;this.size = 0;
}

双向链表的清空方法可不能直接头节点置空,因为直接头节点置空的话,别忘了Java中是某一块空间没有被引用的时候,才会被自动回收掉,但是这是双向链表,中间节点都是互相引用的,所以我们需要每个都手动置空,我们要定义一个curNext引用,指向cur的下一个,防止置空cur的指针域的时候,cur找不到下一个节点了!最后别忘了 size 要等于 0,不然会出问题的!\( ̄︶ ̄*\))

2.8 size 方法

这个方法还是很很简单的,直接 return this.size; 不就可以了吗?


3、LinkedList 的学习 

3.1 认识下 LinkedList 

  • LinkedList 并没有像 ArrayList 一样实现 RandomAccess 接口,所以 LinkedList 并不支持随机访问
  • LinkedList 实现了Cloneable接口,表明 LinkedList 是可以clone的
  • LinkedList 实现了Serializable接口,表明 LinkedList 是支持序列化的
  • LinkedList 在任意位置插入和删除时效率比较高,时间复杂度为O(1)

接着来看一看 LinkedList 里面的成员变量:

3.2 LinkedList 的构造方法

Java 中的 LinkedList 提供了两个构造方法:

方法 解释说明
LinkedList() 无参构造
LinkedList(Collection<? extends E> c) 使用其他集合容器中元素进行构造

使用构造方法例子:

public static void main(String[] args) {// 构造一个空的双向链表LinkedList<Integer> list1 = new LinkedList<>(); // 直接构造List<Integer> list2 = new LinkedList<>(); // 向上转型// 使用ArrayList构造LinkedListArrayList<Integer> arrayList = new ArrayList<>();arrayList.add(1);arrayList.add(2);arrayList.add(3);LinkedList<Integer> list3 = new LinkedList<>(arrayList);List<Integer> list4 = new LinkedList<>(arrayList);
}

至于LinkedList当中还有特别多的方法,小伙伴们下去可以自行查阅手册,这里就不多讲述了!

3.3 LinkedList 的遍历

public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();for (int i = 1; i <= 5; i++) {list.add(i); //add默认是尾插}// 方法1:使用foreach遍历for (int x : list) {System.out.print(x + " ");}System.out.println();// 方法2:使用迭代器遍历->正向遍历ListIterator<Integer> it = list.listIterator();while (it.hasNext()) {System.out.print(it.next() + " ");}System.out.println();// 方法3:使用迭代器遍历->反向遍历ListIterator<Integer> rit = list.listIterator(list.size());while (rit.hasPrevious()) {System.out.print(rit.previous() + " ");}System.out.println();
}

迭代器后期也会逐步接触到,这里能看得懂就ok了!


4、ArrayList 和 LinkedList 的区别

首先我们来说它们的相同点,都是Java中的集合,都是顺序结构,都实现了 List 接口等其他的接口。

重点是他们的区别,也就是不同点!

  • 从存储的角度来说,ArrayList 一定是空间连续的,因为底层是数组,数组是一块连续的存储空间,而 LinkedList 的空间不一定连续,每个节点是依靠节点的指针域进行连接起来的。
  • 从访问元素的角度来说,ArrayList 支持下标随机访问,而 LinkedList 并不支持,而且时间复杂度还是 O(n)。
  • 插入和删除的角度来说,ArrayList 尾插,尾删还好,其他都需要挪动元素了,效率低,时间复杂度是 O(n),而对于 LinkedList 来说,只需要修改指向即可,时间复杂度是 O(1)。
  • 从空间的角度来说,ArrayList 容量不够需要扩容,而 LinkedList 并没有扩容的概念,每次插入都会 new 一个新的节点。
  • 从应用场景的角度来说(目前的知识层面上),ArrayList 更适合频繁用到随机访问,而LinkedList 更适合频繁的插入和删除。

下期预告:【Java 数据结构】栈

【Java 数据结构】双向链表相关推荐

  1. Java数据结构——双向链表、约瑟夫问题

    使用带head头的双向链表实现 –排行榜 管理单向链表的缺点分析: 1) 单向链表, 查找的方向只能是一个方向 ,而双向链 表可以向前或者向后查找. 2) 单向链表不能自我删除,需要靠辅助节点 ,而双 ...

  2. Java数据结构 双向链表

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 文章目录 前言 一.分析思路 二.使用步骤 1.定义节点类 2.添加数据 3.删除数据 总结 前言 如不了解单向链表可以先查看 ...

  3. Java数据结构-双向链表的实现

  4. 图解Java数据结构之双向链表

    上一篇文章说到了单链表,也通过案例具体实现了一下,但是单链表的缺点也显而易见. 单向链表查找的方向只能是一个方向 单向链表不能自我删除,需要靠辅助节点 而双向链表则能够很轻松地实现上面的功能. 何为双 ...

  5. java数据结构 第4章-链表(双向链表)

    文章目录 4.4 双向链表应用实例 4.4.1双向链表的操作分析和实现 1.管理单向链表的缺点分析: 2.分析 双向链表的遍历,添加,修改,删除的操作思路 3.双向链表的代码实现 4.代码运行结果 4 ...

  6. Java数据结构:双向链表的实现

    文章目录 1 双向链表 1.1 双向链表介绍 1.2 双向链表实现思路 2 双向链表实现完整代码 2.1 节点类 Student.java 2.2 双向链表实现类 StudentDoubleLinke ...

  7. 【Java数据结构】通过Java理解和实现——无头双向链表

    ---------------------------------------------------------------------------------------------------- ...

  8. Java——数据结构之双向链表

    文章目录 Java--数据结构之双向链表 1.单链表的实现 (1)定义一个节点类型 (2)头插法 (3)尾插法 (4)根据下标插入节点 (5)查找关键字 (6)删除第一次出现的关键字 (7)删除所有出 ...

  9. java数据结构:双向链表结构与实现

    本教程的内容基本来自于<Java数据结构与算法> 单向链表的缺点在于每个节点只能知道自己与下一个节点,无法寻找到上一个节点.而双向链表可以解决这个问题,通过在节点内部添加一个previou ...

最新文章

  1. wsld2java_脱离spring集成cxf(基于nutz框架)
  2. “开源社区运营就像种菜”,黄东旭谈开源商业化 | 独家
  3. 创业团队如何在低成本的情况下保护网站安全
  4. 基于多分类支持向量机和KNN分类器的大豆叶片病害检测与严重程度测量
  5. Docker的八种用途
  6. ElasticSearch近似匹配调研
  7. python实现三个或三个以上列表之间元素相互组合
  8. 极虎病毒:虎年第一猛毒详解
  9. Android 短视频SDK
  10. 60行C代码实现一个shell
  11. ES-分片路由(routing)
  12. C - Neko does Maths 数论
  13. matlab波形相似系数,如何用matlab计算波形相似系数
  14. windows64位jdk678网盘下载
  15. stio简介及基于阿里云ACK安装Istio
  16. 大学的计算机证书,大学必拿的几个证书是什么?
  17. java如何与RS232进行通信
  18. Java中“/”的使用
  19. Nwafu-OJ-1437 Problem h C语言实习题六——6.进制转换函数设计
  20. 在线绘制流程图工具gliffy

热门文章

  1. 游戏设计总结-1.从立项开始
  2. 在国内怎么玩LOL韩服呢?
  3. 第一个cocos2d程序的代码分析
  4. Linux中的应用程序基础
  5. android自定义阴影的卡片布局,CardView卡片布局的简单使用
  6. C6000的C语言优化
  7. S32K系列S32K144学习笔记——时钟
  8. 如何修复 Wi-Fi 连接不可用的问题?
  9. 文件不小心删除了怎么恢复?
  10. 工业互联网平台助力安全生产的现状与建议 | 工业互联网+安全生产