链表其实也就是 线性表的链式存储结构,与之前讲到的顺序存储结构不同。

我们知道顺序存储结构中的元素地址都是连续的,那么这就有一个最大的缺点:当做插入跟删除操作的时候,大量的元素需要移动。

如图所示,元素在内存中的位置是挨着的,当中有元素被删除,就产生空隙,于是乎后面的元素需要向前挪动去弥补。



正是因为顺序存储有这这个缺点,所以链式存储结构就变得非常的有意义。

一、链表的存储形式

首先,链表是有序的列表,但是在内存中它是这样存储的:

  • 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要添加进来,那么来梳理一下实现的思路:

  1. 先要找到结点2应该添加到的位置,没错就是结点1与结点4之间
  2. 将结点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)、链表的添加的更多相关文章

  1. 深夜学算法之SkipList:让链表飞

    1. 前言 上次写Python操作LevelDB时提到过,有机会要实现下SkipList.摘录下wiki介绍: 跳跃列表是一种随机化数据结构,基于并联的链表,其效率可比拟二叉查找树. 我们知道对于有序 ...

  2. 数据结构与算法 —— 链表linked list(01)

    链表(维基百科) 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer).由于不必须按顺序存储, ...

  3. 数据结构与算法 —— 链表linked list(03)

    继续关于linked list的算法题: 删除排序链表中的重复元素 给定一个排序链表,删除所有重复的元素使得每个元素只留下一个. 案例: 给定 1->1->2,返回 1->2 给定  ...

  4. 数据结构与算法 —— 链表linked list(06)

    回文链表 链接 请检查一个链表是否为回文链表. 进阶:你能在 O(n) 的时间和 O(1) 的额外空间中做到吗? 解题思路: 回文链表的特点就是对称. 把链表放到栈中去,利用栈的先进后出的规则,和原链 ...

  5. 算法与数据结构基础 - 链表(Linked List)

    链表基础 链表(Linked List)相比数组(Array),物理存储上非连续.不支持O(1)时间按索引存取:但链表也有其优点,灵活的内存管理.允许在链表任意位置上插入和删除节点.单向链表结构一般如 ...

  6. 数据结构与算法——链表 Linked List(单链表、双向链表、单向环形链表-Josephu 问题)

    链表是有序的列表,但是在内存中存储图下图所示 链表是以 节点 的方式来存储,是 链式存储 每个节点包含 data 域.next 域,指向下一个节点 链表的各个节点 不一定是连续存储,如上图所示 链表还 ...

  7. 数据结构与算法 —— 链表linked list(02)

    我们继续来看链表的第二道题,来自于leetcode: 两数相加 给定两个非空链表来代表两个非负整数,位数按照逆序方式存储,它们的每个节点只存储单个数字.将这两数相加会返回一个新的链表. 你可以假设除了 ...

  8. 数据结构与算法 —— 链表linked list(05)

    反转一个单链表. 进阶:链表可以迭代或递归地反转.你能否两个都实现一遍? 示例 : 给定这个链表:1->2->3->4->5 返回结果: 5->4->3->2 ...

  9. LeetCode 141. 环形链表(Linked List Cycle) 19

    141. 环形链表 141. Linked List Cycle 题目描述 给定一个链表,判断链表中是否有环. 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 ...

随机推荐

  1. 一分钟搞懂JavaScript中的JSON对象

    JSON(JavaScript Object Notation)是表示值和对象的通用格式. JavaScript 提供了如下方法: JSON.stringify 将对象转换为 JSON. JSON.p ...

  2. Makefile 赋值 函数定义 等小知识点

    1.赋值 == 到用的时候实际才去赋值:= 立刻赋值?= 未赋值才赋值+= 2.多层变量 多层变量引用(各种复杂组合...)a =bb= cc= dd =1$($($($(a)))) 最终等于1 3. ...

  3. where is the storage location of the browser's HTTP cache? disk or memory

    where is the storage location of the browser's HTTP cache? disk or memory HTTP cache & storage l ...

  4. Echarts制作一张全球疫情图

    一.获取全球疫情数据 1)获取API 使用用友提供的新冠肺炎实时数据,登录注册之后可以免费使用. 2)点击用户信息 这里的AIPCODE,复制并保存,用于后续的使用. 3)API的使用 用友有提供一个 ...

  5. 教你吃透CSS的盒子模型(Box Model)

    CSS 盒子模型(Box Model) 所有HTML元素可以看作盒子,在CSS中,"box model"这一术语是用来设计和布局时使用. CSS盒模型本质上是一个盒子,封装周围的H ...

  6. [Python学习笔记]文件的读取写入

    文件与文件路径 路径合成 os.path.join() 在Windows上,路径中以倒斜杠作为文件夹之间的分隔符,Linux或OS X中则是正斜杠.如果想要程序正确运行于所有操作系统上,就必须要处理这 ...

  7. hadoop支持lzo完整过程

    简介 启用lzo 启用lzo的压缩方式对于小规模集群是很有用处,压缩比率大概能降到原始日志大小的1/3.同时解压缩的速度也比较快. 安装lzo lzo并不是linux系统原生支持,所以需要下载安装软件 ...

  8. Spring IoC - 循环依赖

    Spring 复习 3.循环依赖 3.1 定义 循环依赖指多个对象的创建过程中均需要注入对方对象,如下所示 class A{ B b; public A(){ } public A(B b){ thi ...

  9. 一文了解Python的迭代器的实现

    本文对迭代器的解释参考自:https://www.programiz.com/python-programming/iterator 最后自己使用迭代器实现一个公平洗牌类. 博主认为,理论来自实践,假 ...

  10. JAVA学生宿舍管理系统

    转: JAVA学生宿舍管理系统 需要的工具 1.SQL Server 2.Eclipse 3.JDBC连接数据库驱动 https://download.microsoft.com/download/A ...