您的位置:首页 > 编程语言

图解&代码实现:单向链表的创建(直接添加到链表的尾部,不考虑排序)

2020-01-13 18:19 246 查看

链表的基本介绍

链表是有序列表,链表的英文是

LinkedList

链表的

特点:

  1. 链表是以结点方式来存储的,链表是链式存储

  2. 链表中的每个结点都分data域和next域

    data域 : 用来存放数据

    next域:用来指向下一个结点

    下面展示的是 链表再内存中的

    实际结构
    的布局图:

  1. 链表的各个结点不一定是连续存储的

  2. 链表分为带头结点的链表和不带头结点的链表,具体要不要带头结点,根据自己的需求而定

链表的实际案例

实现:管理在线的用户

需求:在某个约定时间,把某个人的好友发送给服务器,但是发送过去的好友编号并不是连续的,要求在服务器上把所有的好友信息按照编号的顺序返回给客服端,因为客服端要定时地向服务器去询问其好友的状态,而服务器需将客服端发送过来的好友按照编号顺序返回,比如说客服端发送过来某一个人的好友编号是 20、1、19、38、5,发给服务器后,要求把这个人的状态返回给客服端,但是要求返回的好友编号顺序是按照从小到大的顺序排列的,比如 1 、 5、19 、20、38,并且不允许查数据库,不能将数据存入数据库中,然后通过order by返回,这样是不允许的。要求在内存中就将这个排序工作做完,有时间和速度要求。

解决方法:使用链表来实现

实现思路:获取到好友Id编号后,按照顺序插入到单向链表中去

带头结点的单链表的

逻辑结构
,如下图所示

根据上图做描述:头结点指向

a1
,
a1
执行
a2
,
a2
指向
a3
,依此类推,若链表中最后一个结点的next域为null就代表链表结束了。

从上图看出

a1
后面就是
a2,a2
后面就是
a3
,依此类推。但这只是逻辑结构,之所以这样画是因为
a1
的下一个next域正好是指向
a2
的,但实际上在内存中,
a1
后面并不一定马上指的就是
a2,a1
的next域只是通过指针指向了
a2
的一个地址,最后将它们连成了一个单链表。该图链表中的各个结点看起来好像是连续存储的,但实际上不是。在做开发做分析时,往往都是这样画链表。实际的存储如下图所示:

单链表的应用实例

使用带head头结点的单向链表实现实现水浒英雄排行榜的管理,即:用链表来管理一些英雄人物

目标:完成对英雄人物的增删改查,也就是对结点的增删改查的操作

创建单链表的形式:

  1. 直接添加在链表尾部,不考虑排序

    即:给我一个英雄对象,我就把它加入到链表最后,不考虑排序

  2. 在添加结点时,考虑排序

    即:在添加英雄时,考虑其排名

创建单链表的示意图(添加) 和 遍历单链表的思路分析:

何为创建?即 添加,添加结点的同时产生链表。

注意:

  1. head头结点不存放具体的数据
  2. head头结点的作用:就是用来连接整个链表,表示单链表的头

根据上图,分析创建单链表的思路:

当我创建一个新的

HeroNode
结点,然后让头结点的next域指向这个新建的
HeroNode
结点,这个新建的
HeroNode
结点中有两个部分,分别具体的数据(即:data域)和next域,这个新建的
HeroNode
结点的next域又指向下一个
HeroNode2
结点,若还有一个
HeroNode3
结点,则让前一个
HeroNode2
结点的next域指向后一个
HeroNode3
结点。这样就形成了一个链表。若最后一个
HeroNode3
结点不再指向任何结点了,那这最后一个
HeroNode3
结点的next域默认为
null

总结创建(添加)单链表步骤:

第一步,先创建一个head结点,作用就是用来连接整个链表,表示单链表的头

第二步,后面每添加一个结点,就直接加入到链表的最后

问题:

  1. 怎么去判断链表中的哪一个是最后的结点呢?

    答:是以判断结点的next域是否为空来决定这个链表是否结束

遍历显示单链表的思路分析:

遍历时,通过定义一个临时变量(或者叫做辅助指针),帮助遍历整个单链表,因为head结点不能动,若head头结点变化了,就找不到链表最后那个结点了.

用代码分析实现单向链表

链表最重要的就是

增删改查

  1. 首先定义一个

    HeroNode
    类,每个
    HeroNode
    对象就是一个结点

    代码实现:

    class HeroNode {
    // 编写属性(共4个)
    // data域 用来存放具体的数据
    public int id;
    public String name;
    public String nickName;
    // next域  用来指向下一个结点
    public HeroNode next;  // 最重要的一个属性
    // 无参构造
    public HeroNode() {
    
    }
    // 带参构造,实现将局部变量的值赋值给成员变量
    public HeroNode(int id,String name,String nickName) {
    // 对属性进行初始化
    this.id  = id;
    this.name = name;
    this.nickName = nickName;
    }
    // 重写toString()方法,为了显示 | 遍历方便
    @Override
    public String toString() {
    return "HeroNode{" +
    "id=" + id +
    ", name='" + name + '\'' +
    ", nickName='" + nickName + '\'' +
    '}';
    }
    }
  2. 创建单链表

    SingleLinkedList
    ,用来管理英雄人物,即:创建单链表来操作结点,

    问题:

    应该怎么把结点添加到单向链表中呢?

    (不考虑编号顺序)

    答:添加时,最重要的是每添加一个结点,直接添加到链表的最后,假如要添加新结点,就

    一定
    要想办法找到链表的最后一个结点。打个比方,如下图:

添加结点到单项链表的思路总结:(不考虑编号顺序)

  1. 找到当前链表的最后一个结点
  2. 将最后一个结点的next域指向要添加的结点(即:新结点)即可

代码展示:

class SingleLinkedList {
// 先初始化一个头结点head,头结点不要动,头结点不存放具体的数据,作用:用来连接整个链表,表示单链表的头
// 初始化HeroNode
private HeroNode head =  new HeroNode(0,"","");
// 编写成员方法addNode(HeroNode heroNode),实现添加结点到单向链表
public void addNode(HeroNode heroNode){ // 无返回值带参
// 因为head结点不能动,因此需要一个临时变量(或者叫做辅助指针) temp
HeroNode temp = head;
// 通过死循环遍历链表,找到链表中的最后一个结点
while(true){
if(temp.next == null){ // 找到链表的最后最后一个结点了,因为temp.next = null 了
break;
}
// 若没有找到最后一个结点就将temp后移,就将temp后移,继续找
temp = temp.next;
}
// 当退出while循环时,说明找到链表的最后一个结点,temp也指向了最后一个结点。
// 将最后一个结点的next域指向要添加的结点(即:新结点)即可
temp.next  = heroNode;
}
// 编写成员方法showList() , 实现显示链表,通过遍历完成
public void showList() {
// 判断链表是否为空
if(head.next == null) { // 说明链表为空
System.out.println("链表为空");
return;
}
//  1. 因为头结点head不能动,因此需要一个临时变量(或者叫做辅助指针)来遍历
// 链表不为空,说明链表中至少有一个数据
HeroNode temp = head.next;
while(true){
// 2. 判断是否遍历到了链表的最后一个结点
if(temp == null) { // 说明已遍历到链表最后一个结点,temp = null,后面就没有结点可以遍历了
break;
}
// 3. 不为空(也就是还没有遍历到链表的最后一个结点),输入结点信息
System.out.println(temp); // 重写了toString方法的,会打印整个结点信息
// 4. 将temp后移,若不后移会造成死循环,一定记住。
temp = temp.next;
}
}
}

实现添加结点到单向链表

addNode(HeroNode heroNode)
的问题:

  1. 如何描述这

    HeroNode temp = head;
    行代码的意义?

    答:有个temp临时变量指向了head头结点,因为head结点不能动,若head结点变化了,那就找不到链表最后的那一个结点了,所以需要这个临时变量.

  2. 如何通过temp结点找到链表中的最后一个结点呢?

    答:通过遍历即可

    画图分析:

  3. 什么时候说明找到链表的最后一个结点了?(

    重要
    )

    答:当

    temp.next = null
    时,说明temp找到了链表中的最后一个结点。比方说:假设链表中只有一个head头结点,则head头结点就是该链表中的最后一个结点,若不是则继续找

  4. 如何来解释

    temp = temp.next
    这行代码?

    答:可以理解为若temp没有指向链表中的最后一个结点,或者说若temp没有找到找到链表中的最后一个结点,就将temp后移

  5. 如何解释将

    temp
    后移?

    画图并描述:

    描述:以上图链表而言,若temp所指向的链表中的第一个结点head不是最后一个结点,就将temp往后移动一下

    temp = temp.next
    ,若temp所指向的链表中的第二个结点
    HeroNode
    还不是最后一个结点,就再次将temp往后移动一下
    temp = temp.next
    ,若temp所指向的链表中的第三个结点
    HeroNode2
    还不是最后一个结点,就再将temp往后移动一下
    temp = temp.next
    ,依此类推,直到temp指向链表中最后一个结点
    HeroNode3
    时发现:
    HeroNode3
    这个结点的next域为空
    temp.next = null
    ,然后就将链表中的最后一个结点
    HeroNode3
    的next域指向要添加的结点
    HeroNode4
    (即:新结点)
    temp.next = HeroNode4
    .

**实现遍历显示单链表时

showList()
**的问题:

  1. 什么时候链表为空?

    答:当

    head.next = null
    时,说明链表为空

  2. 如何描述这

    HeroNode temp = head.next;
    行代码的意义?

    答:因为头结点head不能动,因此需要一个临时变量(或者叫做辅助指针)来遍历,链表不为空,说明链表中至少有一个数据

  3. 什么时候说明遍历到链表最后一个结点了?

    答:当

    temp = null
    时,说明已遍历到链表最后一个结点,temp = null,后面就没有结点可以遍历了。

  4. 为什么要执行

    temp = temp.next;
    ,为什么将temp后移?

    画图并描述:

    描述:因为在遍历显示链表时,输出了链表中一个结点

    HeroNode
    的数据后,要也要继续输出下一个结点
    HeroNode2
    的数据,依次类推,直到遍历输出到链表中最后一个结点
    HeroNode4
    时,说明最后一个结点
    HeroNde4
    的next域为null了,也就是
    temp = null
    了,就
    break
    跳出循环 ,所以需要将temp后移,若不后移则是死循环.

  • 编写测试类,测试一下

    代码展示:

    public class SingleLinkedListTest {
    public static void main(String[] args) {
    // 先创建4个结点
    HeroNode  heroNode = new HeroNode(1,"宋江","及时雨");
    HeroNode  heroNode2 = new HeroNode(2,"卢俊义","玉麒麟");
    HeroNode heroNode3 = new HeroNode(3,"吴用","智多星");
    HeroNode heroNode4 = new HeroNode(4,"林冲","豹子头");
    // 创建单链表
    SingleLinkedList  singleLinkedList = new SingleLinkedList();
    // 加入结点到单链表
    singleLinkedList.addNode(heroNode);
    singleLinkedList.addNode(heroNode2);
    singleLinkedList.addNode(heroNode3);
    singleLinkedList.addNode(heroNode4);
    // 遍历显示链表
    singleLinkedList.showList();
    }
    }
  • 完整代码:

    public class SingleLinkedListTest {
    public static void main(String[] args) {
    // 先创建4个结点
    HeroNode  heroNode = new HeroNode(1,"宋江","及时雨");
    HeroNode  heroNode2 = new HeroNode(2,"卢俊义","玉麒麟");
    HeroNode heroNode3 = new HeroNode(3,"吴用","智多星");
    HeroNode heroNode4 = new HeroNode(4,"林冲","豹子头");
    // 创建单链表
    SingleLinkedList  singleLinkedList = new SingleLinkedList();
    // 加入结点到单链表
    singleLinkedList.addNode(heroNode);
    singleLinkedList.addNode(heroNode2);
    singleLinkedList.addNode(heroNode3);
    singleLinkedList.addNode(heroNode4);
    // 遍历显示链表
    singleLinkedList.showList();
    }
    }// 创建单链表SingleLinkedList,用来管理英雄人物,即:创建单链表来操作结点.
    class SingleLinkedList {
    // 先初始化一个头结点head,头结点不要动,头结点不存放具体的数据,作用:用来连接整个链表,表示单链表的头
    // 初始化HeroNode
    private HeroNode head =  new HeroNode(0,"","");
    // 编写成员方法addNode(HeroNode heroNode),实现添加结点到单向链表
    public void addNode(HeroNode heroNode){ // 无返回值带参
    // 因为head结点不能动,因此需要一个临时变量(或者叫做辅助指针) temp
    HeroNode temp = head;
    // 通过死循环遍历链表,找到链表中的最后一个结点
    while(true){
    if(temp.next == null){ // 找到链表的最后最后一个结点了,因为temp.next = null 了
    break;
    }
    // 若没有找到最后一个结点就将temp后移,就将temp后移,继续找
    temp = temp.next;
    }
    // 当退出while循环时,说明找到链表的最后一个结点,temp也指向了最后一个结点。
    // 将最后一个结点的next域指向要添加的结点(即:新结点)即可
    temp.next  = heroNode;
    }
    // 编写成员方法showList() , 实现显示链表,通过遍历完成
    public void showList() {
    // 判断链表是否为空
    if(head.next == null) { // 说明链表为空
    System.out.println("链表为空");
    return;
    }
    //  1. 因为头结点head不能动,因此需要一个临时变量(或者叫做辅助指针)来遍历
    // 链表不为空,说明链表中至少有一个数据
    HeroNode temp = head.next;
    while(true){
    // 2. 判断是否遍历到了链表的最后一个结点
    if(temp == null) { // 说明已遍历到链表最后一个结点,temp = null,后面就没有结点可以遍历了
    break;
    }
    // 3. 不为空(也就是还没有遍历到链表的最后一个结点),输入结点信息
    System.out.println(temp); // 重写了toString方法的,会打印整个结点信息
    // 4. 将temp后移,若不后移会造成死循环,一定记住。
    temp = temp.next;
    }
    }
    }// 首先定义一个HeroNode类,每个HeroNode对象就是一个结点
    class HeroNode {
    // 编写属性(共4个)
    // data域 用来存放具体的数据
    public int id;
    public String name;
    public String nickName;
    // next域  用来指向下一个结点
    public HeroNode next;  // 最重要的一个属性
    // 无参构造
    public HeroNode() {
    
    }
    // 带参构造,实现将局部变量的值赋值给成员变量
    public HeroNode(int id,String name,String nickName) {
    // 对属性进行初始化
    this.id  = id;
    this.name = name;
    this.nickName = nickName;
    }
    // 重写toString()方法,为了显示 | 遍历方便
    @Override
    public String toString() {
    return "HeroNode{" +
    "id=" + id +
    ", name='" + name + '\'' +
    ", nickName='" + nickName + '\'' +
    '}';
    }
    }

    运行截图:

    问题及优化:

    1. 目前
      addNode(HeroNode heroNode)
      方法没有考虑排序
    2. 要求:添加结点时,考虑排序
    • 点赞
    • 收藏
    • 分享
    • 文章举报
    My-Sun-Shine 发布了27 篇原创文章 · 获赞 12 · 访问量 1501 私信 关注
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: 
    相关文章推荐