【小白学算法】5.链表(linked list)、链表的添加
链表其实也就是 线性表的链式存储结构,与之前讲到的顺序存储结构不同。
我们知道顺序存储结构中的元素地址都是连续的,那么这就有一个最大的缺点:当做插入跟删除操作的时候,大量的元素需要移动。
如图所示,元素在内存中的位置是挨着的,当中有元素被删除,就产生空隙,于是乎后面的元素需要向前挪动去弥补。

正是因为顺序存储有这这个缺点,所以链式存储结构就变得非常的有意义。
一、链表的存储形式
首先,链表是有序的列表,但是在内存中它是这样存储的:

- head:这是头指针,是链表指向第一个结点的指针。无论链表是否为空,头指针均不为空。
- 结点:由data域和next域共同组成,前者存储数据元素本身,后者存储后继位置。
上图所示中,各个结点不一定是连续存放的,最终会有N个节点链接成一个链表,所以就成了链式存储结构。
另外,因为此链表的每个结点中只包含一个next域,所以叫单链表。
二、头指针和头结点
1.头指针
上面提到了头指针,它是链表的必要元素。
因为链表既然也是线性表,所以还是要有头有尾,头指针就是链表中第一个结点的存储位置。
而最后一个结点,指针指向空,通常用NULL表示或者'^'来表示。

2.头结点
与头指针不同,头结点是不一定要有的,得更具实际需求来定。
有时候为了更加方便的操作链表,会在单链表的第一个结点前设一个结点,称为头结点。
加了头结点后,对于第一结点来说,在其之前插入结点或者删除第一结点,操作方式就与其它的结点相同了,不需要进行额外的判断处理。
头结点跟其他结点不同,它的数据域可以不存储任何信息,有必要的话,可以存储一些其他的附加信息,比如线性表的长度等。

现在我们已经知道了单向链表的储存形式以及其构成有哪些,那么现在可以用更直观的图来展示单向链表中数据元素之间的关系了。

三、代码实现一个单链表
1.直接在链表尾部依次添加
比如,现在要用单链表来存储LOL里英雄的信息。如果不带英雄排名顺序的话,那么可以直接依次在链表的末尾增加新的结点即可。
package linkedlist;
public class SingleLinkedListDemo {
public static void main(String[] args) {
// 测试
HeroNode hero1 = new HeroNode(1, "易大师","无极剑圣");
HeroNode hero2 = new HeroNode(2, "李青","盲僧");
HeroNode hero3 = new HeroNode(3, "艾希","寒冰射手");
HeroNode hero4 = new HeroNode(4, "菲奥娜","无双剑姬");
// 创建链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
// 加入对象结点
singleLinkedList.addHero(hero1);
singleLinkedList.addHero(hero2);
singleLinkedList.addHero(hero3);
singleLinkedList.addHero(hero4);
// 显示链表内容
singleLinkedList.linkList();
}
}
// 定义SingleLinkedList 管理英雄
class SingleLinkedList {
// 初始化一个头结点,不要动这个结点。
private HeroNode headNode = new HeroNode(0, "","");
// 添加结点 到 单向链表
// 当不考虑英雄顺序时,找到当前链表的最后一个结点,再讲此结点的next指向新的结点即可
public void addHero(HeroNode heroNode) {
// 因为head结点不能动,所以新建一个临时变量,帮助遍历
HeroNode temp = headNode;
// 开始遍历链表,到最后,找最后的结点
while (true) {
// 等于null时就是最后了
if (temp.next == null) {
break;
}
// 否则就不是最后,将temp继续向后移动
temp = temp.next;
}
// 直到退出循环,此时temp就指向了链表的最后
// 将最后的结点指向这个新的结点
temp.next = heroNode;
}
// 显示链表内容的方法
public void linkList() {
// 判断链表是否为空,空的话就不用继续了
if (headNode.next == null) {
System.out.println("链表为空");
return;
}
HeroNode temp = headNode.next;
while (true) {
// 判断是否已经到了链表最后
if (temp == null) {
break;
}
// 输出结点信息
System.out.println(temp);
// 然后后移temp继续输出下一个结点
temp = temp.next;
}
}
}
// 定义HeroNode,每个HeroNode对象就是一个结点
class HeroNode {
public int no;
public String name;
public String nickname;
public HeroNode next; // 指向下一个结点
// 构造器
public HeroNode(int heroNo, String heroName, String heroNickname) {
this.no = heroNo;
this.name = heroName;
this.nickname = heroNickname;
}
// 为了方便显示,重写toString方法
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
运行一下
HeroNode{no=1, name='易大师', nickname='无极剑圣'}
HeroNode{no=2, name='李青', nickname='盲僧'}
HeroNode{no=3, name='艾希', nickname='寒冰射手'}
HeroNode{no=4, name='菲奥娜', nickname='无双剑姬'}
Process finished with exit code 0
可以看到,链表中的结点是按照添加的顺序依次储存的。
2.考虑顺序的情况下添加链表
上面每个英雄有自己的排名,那么如果我想不关心添加的顺序,在链表中最终都可以按照英雄的排名进行存储,如何实现呢?
这里的话就没有上面直接在末尾添加那么直接了,但是也不算难理解,看个示意图。

如图所示,现在有一个结点2要添加进来,那么来梳理一下实现的思路:
- 先要找到结点2应该添加到的位置,没错就是结点1与结点4之间
- 将结点1的next指向结点2,再将结点2的next指向结点4即可
是不是很简单,不过为了实现第2点,我们还是需要借助一个辅助变量temp,可以把它看作一个指针。

temp会从头开始遍历链表,来找到结点2应该添加到的位置,此时会停在结点1,那么:
- 结点2.next = temp.next,这样可以将结点2指向结点4
- temp.next = 结点2,这样可以将结点1指向结点2
这样我们的目的就达成了,代码也就知道怎么去改了。
决定在SingleLinkedList类中,增加一个新方法,可以跟据英雄的排名进行添加。
package linkedlist;
public class SingleLinkedListDemo {
public static void main(String[] args) {
// 测试
HeroNode hero1 = new HeroNode(1, "易大师","无极剑圣");
HeroNode hero2 = new HeroNode(2, "李青","盲僧");
HeroNode hero3 = new HeroNode(3, "艾希","寒冰射手");
HeroNode hero4 = new HeroNode(4, "菲奥娜","无双剑姬");
// 创建链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
// 加入对象结点
singleLinkedList.addByNo(hero1);
singleLinkedList.addByNo(hero4);
singleLinkedList.addByNo(hero2);
singleLinkedList.addByNo(hero3);
// 显示链表内容
singleLinkedList.linkList();
}
}
// 定义SingleLinkedList 管理英雄
class SingleLinkedList {
// 初始化一个头结点,不要动这个结点。
private HeroNode headNode = new HeroNode(0, "","");
// 添加结点 到 单向链表
// 当不考虑英雄顺序时,找到当前链表的最后一个结点,再讲此结点的next指向新的结点即可
public void addHero(HeroNode heroNode) {
// 因为head结点不能动,所以新建一个临时变量,帮助遍历
HeroNode temp = headNode;
// 开始遍历链表,到最后,找最后的结点
while (true) {
// 等于null时就是最后了
if (temp.next == null) {
break;
}
// 否则就不是最后,将temp继续向后移动
temp = temp.next;
}
// 直到退出循环,此时temp就指向了链表的最后
// 将最后的结点指向这个新的结点
temp.next = heroNode;
}
// 添加方法2:根据排名将英雄按照排名顺序依次放到对应位置
public void addByNo(HeroNode heroNode) {
// 借助temp遍历链表,找到添加位置的前一个结点
HeroNode temp = headNode;
// 考虑一种情况:当添加的位置已经存在对应排名的英雄,则不能添加
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.no > heroNode.no) { // 位置找到,在temp的后面添加
break;
} else if (temp.next.no == heroNode.no) { // 目标添加位置,已经存在对应编号,不能添加
flag = true;
break;
}
temp = temp.next; // 继续后移
}
// 跳出循环,进行添加操作
if (flag) {
System.out.printf("准备插入的英雄编号%d已存在,不可加入\n", heroNode.no);
} else {
// 可以正常插入到链表
heroNode.next = temp.next;
temp.next = heroNode;
}
}
// 显示链表内容的方法
public void linkList() {
// 判断链表是否为空,空的话就不用继续了
if (headNode.next == null) {
System.out.println("链表为空");
return;
}
HeroNode temp = headNode.next;
while (true) {
// 判断是否已经到了链表最后
if (temp == null) {
break;
}
// 输出结点信息
System.out.println(temp);
// 然后后移temp继续输出下一个结点
temp = temp.next;
}
}
}
// 定义HeroNode,每个HeroNode对象就是一个结点
class HeroNode {
public int no;
public String name;
public String nickname;
public HeroNode next; // 指向下一个结点
// 构造器
public HeroNode(int heroNo, String heroName, String heroNickname) {
this.no = heroNo;
this.name = heroName;
this.nickname = heroNickname;
}
// 为了方便显示,重写toString方法
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
在main方法中,我们打乱结点添加的顺序,运行一下,看看最终链表里是不是按照影响的排名顺序存储的
HeroNode{no=1, name='易大师', nickname='无极剑圣'}
HeroNode{no=2, name='李青', nickname='盲僧'}
HeroNode{no=3, name='艾希', nickname='寒冰射手'}
HeroNode{no=4, name='菲奥娜', nickname='无双剑姬'}
Process finished with exit code 0
结果正确,符合预期,不管先添加谁,最终在链表里都是按照英雄的排名来存放。
继续测试,我重复添加结点3,看下会如何。
// 加入对象结点
singleLinkedList.addByNo(hero1);
singleLinkedList.addByNo(hero4);
singleLinkedList.addByNo(hero2);
singleLinkedList.addByNo(hero3);
singleLinkedList.addByNo(hero3);
运行一下:
准备插入的英雄编号3已存在,不可加入
HeroNode{no=1, name='易大师', nickname='无极剑圣'}
HeroNode{no=2, name='李青', nickname='盲僧'}
HeroNode{no=3, name='艾希', nickname='寒冰射手'}
HeroNode{no=4, name='菲奥娜', nickname='无双剑姬'}
Process finished with exit code 0
提示了已经存在了,不可加入。
下面会继续单链表的修改和删除等。
【小白学算法】5.链表(linked list)、链表的添加的更多相关文章
- 深夜学算法之SkipList:让链表飞
1. 前言 上次写Python操作LevelDB时提到过,有机会要实现下SkipList.摘录下wiki介绍: 跳跃列表是一种随机化数据结构,基于并联的链表,其效率可比拟二叉查找树. 我们知道对于有序 ...
- 数据结构与算法 —— 链表linked list(01)
链表(维基百科) 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer).由于不必须按顺序存储, ...
- 数据结构与算法 —— 链表linked list(03)
继续关于linked list的算法题: 删除排序链表中的重复元素 给定一个排序链表,删除所有重复的元素使得每个元素只留下一个. 案例: 给定 1->1->2,返回 1->2 给定 ...
- 数据结构与算法 —— 链表linked list(06)
回文链表 链接 请检查一个链表是否为回文链表. 进阶:你能在 O(n) 的时间和 O(1) 的额外空间中做到吗? 解题思路: 回文链表的特点就是对称. 把链表放到栈中去,利用栈的先进后出的规则,和原链 ...
- 算法与数据结构基础 - 链表(Linked List)
链表基础 链表(Linked List)相比数组(Array),物理存储上非连续.不支持O(1)时间按索引存取:但链表也有其优点,灵活的内存管理.允许在链表任意位置上插入和删除节点.单向链表结构一般如 ...
- 数据结构与算法——链表 Linked List(单链表、双向链表、单向环形链表-Josephu 问题)
链表是有序的列表,但是在内存中存储图下图所示 链表是以 节点 的方式来存储,是 链式存储 每个节点包含 data 域.next 域,指向下一个节点 链表的各个节点 不一定是连续存储,如上图所示 链表还 ...
- 数据结构与算法 —— 链表linked list(02)
我们继续来看链表的第二道题,来自于leetcode: 两数相加 给定两个非空链表来代表两个非负整数,位数按照逆序方式存储,它们的每个节点只存储单个数字.将这两数相加会返回一个新的链表. 你可以假设除了 ...
- 数据结构与算法 —— 链表linked list(05)
反转一个单链表. 进阶:链表可以迭代或递归地反转.你能否两个都实现一遍? 示例 : 给定这个链表:1->2->3->4->5 返回结果: 5->4->3->2 ...
- LeetCode 141. 环形链表(Linked List Cycle) 19
141. 环形链表 141. Linked List Cycle 题目描述 给定一个链表,判断链表中是否有环. 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 ...
随机推荐
- yarn & macOS & upgrade
yarn & macOS https://yarnpkg.com/zh-Hans/docs/install#mac-stable $ brew install yarn $ brew upgr ...
- svg 矩阵转换
svg 矩阵转换 https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix https://develope ...
- Puppeteer: 虚拟键盘
文档 main.js const pptr = require('puppeteer'); const gotoUrl = 'http://127.0.0.1:5500/index.html'; (a ...
- Baccarat是如何运用去中心化治理模式的?
区块链的出现,让大家看到了去中心化的可能.去中心化的数字资产从最初的默默无闻,一路起起伏伏发展了十年,逐渐成为了大众认可的价值存储方式.去中心化的金融,使数字资产的生态建设者意识到,即使没有中心化的金 ...
- HTTP 协议的前世今生
尽人事,听天命.博主东南大学研究生在读,热爱健身和篮球,正在为两年后的秋招准备中,乐于分享技术相关的所见所得,关注公众号 @ 飞天小牛肉,第一时间获取文章更新,成长的路上我们一起进步 本文已收录于 C ...
- CMD 中运行 xx 命令提示 不是内部或外部命令,也不是可运行的程序或批处理文件的问题
出现这个问题的原因一般有2个 这个命令依赖某个软件,而你又没有安装 这里你只需要去下载安装好对应的软件,基本上就可以解决上面的问题了. 软件安装好了,但是需要配置环境变量 第二个原因就按照下图,去设置 ...
- 两年Java,去字节跳动写Python和Go
前言 2019年5月,在收到offer邮件的那一刻,我仍然不敢相信自己这一番际遇.经历了七场面试,终于得偿所望,拿到了字节跳动的offer. 做加入大厂的决定并不是巧合.在多年的职业生涯里,我曾多次对 ...
- Python 爬虫使用动态切换ip防止封杀
对于爬虫被封禁 ! 爬虫一般来说只要你的ip够多,是不容易被封的. 一些中小网站要封杀你,他的技术成本也是很高的,因为大多数网站没有vps,他们用的是虚拟空间或者是sae,bae这样的paas云. 其 ...
- JVM系列(四):java方法的查找过程实现
经过前面几章的简单介绍,我们已经大致了解了jvm的启动框架和执行流程了.不过,这些都是些无关痛痒的问题,几行文字描述一下即可. 所以,今天我们从另一个角度来讲解jvm的一些东西,以便可以更多一点认知. ...
- 开源OA办公系统的“应用市场”,能够为协同办公开拓什么样的“前路”?
在我们的日常生活中,应用市场这个词,总是与智能手机划上等号,不管使用的是iPhone还是安卓,总会接触到手机上的APP应用市场,我们可以在应用市场中,选择自己所需要的APP应用软件,下载使用后,可以让 ...