Java学习指南(15) 链表
本文是《Java学习指南》原书的网络版,作者邵发,拥有本书的全部权利。相关视频课程在此查看。
目录
第15章 链表
15.1 容器
15.2 链表
15.3 插入节点
15.4 有头链表
15.5 链表与容器
15.6 ArrayList
第15章 链表
15.1 容器
容器 (Container),就是能存放多个对象的东西。容器是一个设计上的术语,并非语法概念。
比如说,数组就是一个容器,数组里可以容纳多个对象。
例如,
Student[] ss = new Student[4];
则ss是一个容器,里面最多可以存放4个对象
ss[0] = new Student("20170001", "邵发");
ss[1] = new Student("20170002", "小张");
ss[2] = new Student("20170003", "小王");
ss[3] = null;
此时容器里有3个Student对象,还剩1个空闲位置。
其实可以形象地理解为,一排4个座位,现在已经坐了3个人,空一个位置。
邵 |
张 |
王 |
现在考虑数组的插入操作。假设又有一个学生小李,他希望坐在第2个位置。代码如下,
ss[3] = ss[2]; // “王”挪一个位置
ss[2] = ss[1]; // “张”挪一个位置
ss[1] = new Student("20170004", "李");
现在,4个位置已经坐满,如下所示,
邵 |
李 |
张 |
王 |
在上面的操作中,插入一个元素,需要移动两个元素的位置。试想,如果有100个元素,那一个插入操作可能就要做99次的数据移动,这个代价是很大的。
所以,数组容器有明显的缺点。缺点一,数组的容量是固定的,无法动态调整。缺点二,数组元素的插入和删除都比较复杂。(一人插队,后面所有的人都要挪动位置)
我们希望有一种新的容器类型,可以存放对象,又能克服数组容器的缺陷,这就是这一章的所要介绍的:链表容器。
15.2 链表
链表 ( Linked List ) ,也是一种容器。在普通高校里的“数据结构与算法”这门课里,一般都有链表这种数组结构的介绍。
什么叫链表呢,其实就是一种链状的组织结构。比如,现在有4猴子(Monkey),它们目前是孤立存在的对象,互相没有联系。
现在,让它们串联起来,形成一个链表结构。方法是,让每只猴子握住前一阵猴子尾巴,从而形成一个队伍。如下所示,
这种串起来的逻辑结构就称之为“链表”。
15.2.1 链表的构造
下面看来,链表在Java代码里是怎么表示的。首先,定义一个Monkey类表示猴子,如下,
public class Monkey
{public int id; // 编号public String name; // 名字public Monkey next; // 它后面的猴子public Monkey(int id, String name){this.id = id;this.name = name;}@Overridepublic String toString(){return String.format("(%s, %s)", name, id);}
}
提示:Monkey类应重写toString()方法,具体参考前面的11.4节。
其中,next表示下一只猴子。如果对这种写法有点生疏,可以回顾一下8.4节的内容。
现在,创建4个Monkey对象,
Monkey m1 = new Monkey(1, "圆圆");
Monkey m2 = new Monkey(2, "方方");
Monkey m3 = new Monkey(3, "角角");
Monkey m4 = new Monkey(4, "朱朱");
则m1, m2, m3, m4是四只猴子,下面把它们依次串起来,
m1.next = m2; // m2握住m1的尾巴
m2.next = m3; // m3握住m2的尾巴
m3.next = m4; // m4握住m3的尾巴
m4.next = null; // m4后面没有猴子
如此,m1,m2,m3,m4这四个对象便有了逻辑上的串联关系,形成一条链状的结构。这种逻辑结构就是链表。
15.2.2 几个概念
链表:这种以“链”状形式串起来的结构。链表不是表,而一条链子。
节点:链表里的每个对象(Node),称为节点。
链表头:最前面的那个节点,称为链表头,或称为头节点。
15.2.3 链表的遍历
链表的头节点在链表中起到至关重要的作用,因为有了头节点,就可以顺着链条一直往下把每个节点遍历出来。
比如,前面构造的链表的头节点是m1。从m1一直往后遍历,m1.next是m2,m2.next是m3,m3.next是m4。m4.next是null,说明m4是最后一个节点(尾节点)。
以下代码展示了链表的一般遍历方法,
Monkey node = m1; // 从头节点开始
while( node != null)
{System.out.println("节点: " + node.name)node = node.next;
}
也就是说,只要有了头节点,就可以遍历出链表里的每一个节点,直到末尾。
15.3 插入节点
现在来考虑一下,怎么向链表中添加一个节点。
假设现在链表中已经有了4只猴子,编号依次为①②③④,示意图如下,
现在要往这个链表里添加第⑤号猴子。有几种方案,可以把它附加到末尾,也可以放在①②之间,等等。下面分别讨论一下各种方案应该如何实现。
15.3.1 添加到末尾
要把⑤号节点添加链表的末尾,首先需要找到尾节点。
// 先找到尾节点
Monkey tail = m1;
while( tail.next != null )
{tail = tail.next;
}// 把5号节点附加到末尾
Monkey m5 = new Monkey(5, "花花");
tail.next = m5;
其中,先用一个遍历,找到链表的尾节点。然后,tail.next = m5,将新节点挂在尾部。
至此,这个链子上有了5个节点,示意图发下,
15.2 添加到前面
也可以把⑤号节点添加到头节点之后。示例代码如下,
Monkey head = m1;
m5.next = head.next; // 把②挂到⑤之后
head.next = m5; // 把⑤挂到①之后
最终结构示意图如下,
注意,在这段代码中,如果改成下面的操作顺序,是错误的。示例如下,
Monkey head = m1;
head.next = m5; // 把 ⑤ 挂到 ① 之后
m5.next = head.next; // 出错!此时head.next是 ⑤
这样的代码看起来和上面一样,只颠倒了两行代码的顺序,为什么就不对了呢?要仔细梳理清楚。
15.3 添加到指定节点之后
还有一种情形,就是插入到指定的节点之后。比如,要求插入到值为2的节点之后。其基本的实现逻辑是:遍历链表,找到目标节点,然后把新节点插入到目标节点之后。
Monkey node = m1;
while(node != null) // 遍历每个节点
{if(node.id == 2) // 找到目标节点{// 附加到目标节点之后m5.next = node.next;node.next = m5;break;}node = node.next;
}
小结一下,相对于数组而言,链表有以下优点:
- 可以有无限多个节点,长度不限制
- 插入一个节点,并不需要挪动后面节点的位置
15.4 有头链表
为了简化链表的相关算法,可以用一种特殊的设计来构造链表,即所谓的“有头链表”。有头链表,使用一个假节点来作为链表的头部,其他有效的数据节点挂在假节点的后面。
示意图如下:
在此图中,第一节点不含有效数据,它是一个假节点。后面4个节点都是有效的数据节点。按这种形式构造出来的链表,其插入和删除算法都比较简单。
15.4.1 有头链表的构造
需要创建一个假节点,不含真实数据的节点。例如,创建一只石猴作为头节点,
Monkey head = new Monkey(0, "石猴");
然后再把4个有效节点挂在假节点之后,
head.next = m1;
m1.next = m2;
m2.next = m3;
m3.next = m4;
m4.next = null;
如此,便构造了一个含有5个节点的链表。头节点是一只石猴,后面4个节点是真的猴子。
15.4.2 有头链表的遍历
在遍历时,不应包括头节点,因为头节点里不含有效数据。代码如下,
Monkey m = head.next;
while( m != null)
{// 。。。m = m.next;
}
15.4.2 向有头链表的插入节点
最快的方法,是把新节点直接挂在头节点的后面。示例代码如下,
m5.next = head.next;
head.next = m5;
插入⑤号节点之后,链表的逻辑结构示意如下,
15.4.3 从有头链表中删除节点
例如,删除2号节点,
Monkey node = head;
while( node.next != null )
{if(node.next.id == 2) // 找到目标节点{// 删除该节点node.next = node.next.next;break;}node = node.next;
}
其中,有一行有点难度,
node.next = node.next.next;
在删除之前,node是①号节点,node.next是②号节点, node.next.next是③号节点。
在删除之后,node是①号节点,node.next是③号节点,所以就达到了删除②号节点的效果。
15.5 链表与容器
严格的说,链表还不是容器,它只是实现容器的一种方式。下面将演示怎么用链表这种数据结构来实现容器。
在第15.1节里已经讲过,所谓容器,就是一种可以存放对象的东西。创建一个容器之后,就可以把多个对象塞到容器里。如上图所示。
下面,我们将创建一个容器类MonkeyList,使用它来存储Monkey对象。该类的大致结构如下:
public class MonkeyList
{// 添加一个对象public void add ( Monkey m ){}// 按编号来查询public Monkey get ( int id){}// 打印输出所有的对象public void showAll(){}
}
在MonkeyList类里,我们将实现3个方法,add()方法用于向容器里放入一个Monkey对象,get()方法用于按编号查询,showAll()方法用于显示容器里的所有对象。
15.5.1 容器的实现
这样的容器,内部可以用数组结构来实现,也可以使用链表结构来实现。下面使用有头链表的设计来实现这个容器。
首先,添加一个假节点作为头节点;然后,实现add()方法,把新增的节点挂在head节点的后面。
public class MonkeyList
{private Monkey head = new Monkey(0, "石猴");// 添加一个对象public void add ( Monkey m ){m.next = head.next;head.next = m;}public void showAll(){Monkey m = head.next;while( m != null){System.out.println("容器中的对象: " + m);m = m.next;}}// 其他方法的实现代码省略,参考网盘源码
}
15.5.2 容器的使用
有了这个类之后,便可以按如下的方式来使用这个容器,
public static void main(String[] args){// 4只猴子Monkey m1 = new Monkey(101, "圆圆");Monkey m2 = new Monkey(102, "方方");Monkey m3 = new Monkey(103, "角角");Monkey m4 = new Monkey(104, "朱朱");MonkeyList monkeys = new MonkeyList();monkeys.add( m1 );monkeys.add( m2 );monkeys.add( m3 );monkeys.add( m4 );monkeys.showAll();
}
此时,对调用者而言,MonkeyList就是一个容器类,可以存放多个Monkey对象。
而且,这个容器的存储容量似乎是没有上限的,无论有多少只猴子,都可以塞进去。这就是链表这种结构的优点。
15.5.3 黑盒设计
黑盒(Black Box),是一种设计术语。所谓黑盒设计,就是在设计一个类或一个模块时,把内部想象成不透明的。
调用者在使用时,只需关心如何使用它,而不必关心(也不能关心)内部的实现。对于调用者而言,无论这个容器内部是丑是美、里面用的是链表还是数组,与我何干?
15.6 ArrayList
ArrayList,是Java自带的一个基础工具类。兼具Array和List的双重特点,可以称为数组链表。
ArrayList是一个容器,可以存储任何对象。正如在上一节课所说的,对于这种已经写好的容器类,我们首要关心的是如何使用它,而不是关心它的内部实现。(实现原理在高级语法里的“泛型”一章里讲解)
ArrayList提供了丰富的方法操作,比如,add()方法可以添加对象,remove()方法可以删除对象,等等。
先看一个例子,学习一下它的使用。
比如,有3个学生对象,
Student s1 = new Student("20170001", "邵发");
Student s2 = new Student("20170002", "小张");
Student s3 = new Student("20170003", "小王");
现在创建一个容器对象,
ArrayList container = new ArrayList();
然后把3个学生对象添加到容器里,
container.add( s1 );
container.add( s2 );
container.add( s3 );
接下来,可以像数组一样遍历容器里的所有对象,
for(int i=0; i< container.size(); i++)
{Student s = (Student) container.get(i);System.out.println("遍历得到: " + s );
}
如果要删除某个对象,可以用remove()方法,示例代码如下,
container.remove( 1 );
在将来的工程应用中,我们一般不会自己来定义一个链表,而是直接使用Java自带的各种现成的工具类,比如ArrayList类。关于ArrayList类的实现原理和使用细节,在后面高级语法的泛型一章里有详细说明。
Java学习指南(15) 链表相关推荐
- 《Java学习指南》—— 1.4 设计安全
本节书摘来异步社区<Java学习指南>一书中的第1章,第1.4节,作者:[美]Patrick Niemeyer , Daniel Leuck,更多章节内容可以访问云栖社区"异步社 ...
- Java学习指南从入门到入土
Java学习指南从入门到入土 本身其实只是刚刚入门,只是经历了两年时间的风吹雨打,经历了各种bug的折磨和学习各种框架的辛酸,才有得现有的 刚刚入门.有句老话说的好叫做 从入门到放弃,人生不易要及时放 ...
- 视频教程-Java学习指南(Swing高级篇)-Java
Java学习指南(Swing高级篇) 邵发,清华大学毕业,从业软件开发十余年,自2015年起致力于C/C++/Java等基础教育领域,希望能通过提高每一个个体的素质来推动中国IT业的整体发展.代表作: ...
- 《Java学习指南》—— 1.1进入Java世界
本节书摘来异步社区<Java学习指南>一书中的第1章,第1.1节,作者:[美]Patrick Niemeyer , Daniel Leuck,更多章节内容可以访问云栖社区"异步社 ...
- 真是恍然大悟啊!免费Java高级工程师学习资源,详细的Java学习指南
前言 小编看过很多讲Git的文章但感觉还是不够详细,所以出现了这篇文章. 今天来说一说Git命令全方位学习 文章目录 Git是什么? Git的相关理论基础 日常开发中,Git的基本常用命令 Git进阶 ...
- Java学习lesson 15
*Set集合 一个包含重复的元素collection,并且最多包含一个null元素,此类实现Set接口,有哈希表支持,Java中的预定义类型如String.Integer都可以在集合内使用:但对于自己 ...
- JAVA学习笔记 15 - 函数式接口、Lambda表达式和方法引用
本文是Java基础课程的第十五课.主要介绍在JDK8中,Java引入的部分新特性,包括函数式接口.Lambda表达式和方法引用.这些新特性使得Java能够在按照面向对象思想进行开发的基础上,融合函数式 ...
- 如何化身BAT面试收割机?详细的Java学习指南
前言 正值金九银十面试旺季,现在的面试官一天少说得看几百份简历,你投出去的简历如果没有特别吸引人的点和新颖突出,毫无疑问你的简历不是在垃圾桶就是在回收站里边待着了,那么什么样的简历才能吸引到面试官的眼 ...
- 成功跳槽字节跳动,详细的Java学习指南
最近几天,有的人在偷偷地买蚂蚁的基金,有的人却偷偷的在蚂蚁面试. 最近确实是面试的好时候.大家都奔着大厂去的,最近也分享了好多大厂的面经了,什么阿里.字节.京东.美团.百度.腾讯.滴滴.网易-已经数不 ...
- java学习(15):巩固练习
//任务 1 //编写控制台java程序,使用Scanner 对象相关方法从 //控制台接收用户输入如下数据并使用相关的局部变量接收,在控制台打印输出. //老师的姓名:老师的性别:老师的工资:老师的 ...
最新文章
- delete 误删了全表数据
- 写5个不同的自己的函数,来截取一个全路径的文件的扩展名,允许封装php库中已有的函数。
- 顺丰负债300亿就压力山大,而万达曾经负债4000亿却稳如泰山
- 以前的报表都白做了!app上做可视化数据分析,这个方法太强了
- 嵌入式linux只读保护,如何使用squashfs只读文件系统制作Linux系统文件
- Visual Studio的语法着色终于调得赏心悦目
- 附件计算器中的MC、MR、MS、M+作用
- 机器学习之熵【从定义到代码】
- arcgis的numpy模块_数据分析之numpy模块
- C#敏感词过滤算法实现
- 初学者最容易学的六种编程语言
- IDEA中修改html页面后在浏览器不生效的解决方法
- STM32——TIM简介与TIM中断
- 为什么戴耳机听歌时候耳朵痛?那是你没用到对的耳机
- Redis的五种基础数据结构和三种高级数据结构
- 极客学院 Android 系统体系教程
- glassfish java ee_GlassFish 3.1.2发布 开源的JavaEE应用服务器
- 360wifi在linux系统如何使用,在树莓派上使用360WIFI(也适用于小米、百度、腾讯WIFI)...
- shogun-toolbox的安装与问题总结
- Android studio修改svn地址的问题