本文是《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) 链表相关推荐

  1. 《Java学习指南》—— 1.4 设计安全

    本节书摘来异步社区<Java学习指南>一书中的第1章,第1.4节,作者:[美]Patrick Niemeyer , Daniel Leuck,更多章节内容可以访问云栖社区"异步社 ...

  2. Java学习指南从入门到入土

    Java学习指南从入门到入土 本身其实只是刚刚入门,只是经历了两年时间的风吹雨打,经历了各种bug的折磨和学习各种框架的辛酸,才有得现有的 刚刚入门.有句老话说的好叫做 从入门到放弃,人生不易要及时放 ...

  3. 视频教程-Java学习指南(Swing高级篇)-Java

    Java学习指南(Swing高级篇) 邵发,清华大学毕业,从业软件开发十余年,自2015年起致力于C/C++/Java等基础教育领域,希望能通过提高每一个个体的素质来推动中国IT业的整体发展.代表作: ...

  4. 《Java学习指南》—— 1.1进入Java世界

    本节书摘来异步社区<Java学习指南>一书中的第1章,第1.1节,作者:[美]Patrick Niemeyer , Daniel Leuck,更多章节内容可以访问云栖社区"异步社 ...

  5. 真是恍然大悟啊!免费Java高级工程师学习资源,详细的Java学习指南

    前言 小编看过很多讲Git的文章但感觉还是不够详细,所以出现了这篇文章. 今天来说一说Git命令全方位学习 文章目录 Git是什么? Git的相关理论基础 日常开发中,Git的基本常用命令 Git进阶 ...

  6. Java学习lesson 15

    *Set集合 一个包含重复的元素collection,并且最多包含一个null元素,此类实现Set接口,有哈希表支持,Java中的预定义类型如String.Integer都可以在集合内使用:但对于自己 ...

  7. JAVA学习笔记 15 - 函数式接口、Lambda表达式和方法引用

    本文是Java基础课程的第十五课.主要介绍在JDK8中,Java引入的部分新特性,包括函数式接口.Lambda表达式和方法引用.这些新特性使得Java能够在按照面向对象思想进行开发的基础上,融合函数式 ...

  8. 如何化身BAT面试收割机?详细的Java学习指南

    前言 正值金九银十面试旺季,现在的面试官一天少说得看几百份简历,你投出去的简历如果没有特别吸引人的点和新颖突出,毫无疑问你的简历不是在垃圾桶就是在回收站里边待着了,那么什么样的简历才能吸引到面试官的眼 ...

  9. 成功跳槽字节跳动,详细的Java学习指南

    最近几天,有的人在偷偷地买蚂蚁的基金,有的人却偷偷的在蚂蚁面试. 最近确实是面试的好时候.大家都奔着大厂去的,最近也分享了好多大厂的面经了,什么阿里.字节.京东.美团.百度.腾讯.滴滴.网易-已经数不 ...

  10. java学习(15):巩固练习

    //任务 1 //编写控制台java程序,使用Scanner 对象相关方法从 //控制台接收用户输入如下数据并使用相关的局部变量接收,在控制台打印输出. //老师的姓名:老师的性别:老师的工资:老师的 ...

最新文章

  1. delete 误删了全表数据
  2. 写5个不同的自己的函数,来截取一个全路径的文件的扩展名,允许封装php库中已有的函数。
  3. 顺丰负债300亿就压力山大,而万达曾经负债4000亿却稳如泰山
  4. 以前的报表都白做了!app上做可视化数据分析,这个方法太强了
  5. 嵌入式linux只读保护,如何使用squashfs只读文件系统制作Linux系统文件
  6. Visual Studio的语法着色终于调得赏心悦目
  7. 附件计算器中的MC、MR、MS、M+作用
  8. 机器学习之熵【从定义到代码】
  9. arcgis的numpy模块_数据分析之numpy模块
  10. C#敏感词过滤算法实现
  11. 初学者最容易学的六种编程语言
  12. IDEA中修改html页面后在浏览器不生效的解决方法
  13. STM32——TIM简介与TIM中断
  14. 为什么戴耳机听歌时候耳朵痛?那是你没用到对的耳机
  15. Redis的五种基础数据结构和三种高级数据结构
  16. 极客学院 Android 系统体系教程
  17. glassfish java ee_GlassFish 3.1.2发布 开源的JavaEE应用服务器
  18. 360wifi在linux系统如何使用,在树莓派上使用360WIFI(也适用于小米、百度、腾讯WIFI)...
  19. shogun-toolbox的安装与问题总结
  20. Android studio修改svn地址的问题

热门文章

  1. 启动Vue项目设置默认浏览器为Chrome
  2. c语言身份证号码验证
  3. 方波的产生——555 产生方波
  4. 夕四今晚加班到2点30,而王二还不打算走《打工人的那些事》
  5. Markdown入门指南【我为什么要推荐你学习Markdown?】
  6. 数学通道的应用(六)-补偿气缸压力
  7. 关于《数据出境安全评估办法》,来看看3位行业专家的不同视角
  8. mysql删除一行_MySql删除表中一行的实操方法
  9. python查询12306余票_Python之12306余票查询
  10. 将FTP空间通过网络映射到本地电脑上