文章目录

  • 单链表
    • 什么是单链表,链式存储结构详解
    • 链表的节点
    • 头节点,头指针和首元节点
  • 单链表的实现
    • 1.尾部添加新节点
      • 思路分析
      • 代码实现
      • 注意事项
    • 2.按照编号插入新节点
      • 思路分析
      • 代码实现
      • 注意事项
    • 3.按照编号修改节点信息
      • 思路分析
      • 代码演示
    • 4.删除节点
      • 思路分析
      • 代码演示
      • 注意事项
  • 常用对链表的操作
    • 1.求单链表中有效节点的个数
      • 代码演示
    • 2.查找单链表中的倒数第k个节点
      • 思路分析
      • 代码演示
      • 注意事项
    • 3.单链表的反转
      • 思路分析
      • 代码演示
  • 完整Demo

单链表


什么是单链表,链式存储结构详解

链表,别名链式存储结构或单链表,用于存储逻辑关系为 “一对一” 的数据。与顺序表不同,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置是随机的。

例如,使用链表存储 {1,2,3},数据的物理存储状态如图 1 所示:

我们看到,图 1 根本无法体现出各数据之间的逻辑关系。对此,链表的解决方案是,每个数据元素在存储时都配备一个指针,用于指向自己的直接后继元素。如图 2 所示:

像图 2 这样,数据元素随机存储,并通过指针表示数据之间逻辑关系的存储结构就是链式存储结构。


链表的节点

链表中每个数据的存储都由以下两部分组成:

  1. 数据元素本身,其所在的区域称为数据域
  2. 指向直接后继元素的指针,所在的区域称为指针域

即链表中存储各数据元素的结构如图 3 所示:

图 3 所示的结构在链表中称为节点。也就是说,链表实际存储的是一个一个的节点,真正的数据元素包含在这些节点中,如图 4 所示:


头节点,头指针和首元节点

其实,图 4 所示的链表结构并不完整。一个完整的链表需要由以下几部分构成:

  1. 头指针(含义同下问的辅助指针):一个普通的指针,它的特点是永远指向链表第一个节点的位置。很明显,头指针用于指明链表的位置,便于后期找到链表并使用表中的数据;
  2. 节点:链表中的节点又细分为头节点、首元节点和其他节点:
    头节点:其实就是一个不存任何数据的空节点,通常作为链表的第一个节点。对于链表来说,头节点不是必须的,它的作用只是为了方便解决某些实际问题;
    首元节点:由于头节点(也就是空节点)的缘故,链表中称第一个存有数据的节点为首元节点。首元节点只是对链表中第一个存有数据节点的一个称谓,没有实际意义;
    其他节点:链表中其他的节点;

因此,一个存储 {1,2,3} 的完整链表结构如图 5 所示:

注意:链表中有头节点时,头指针指向头节点;反之,若链表中没有头节点,则头指针指向首元节点。



单链表的实现

使用带 head 头的单向链表实现:王者荣耀英雄排行榜管理完成对英雄人物的增删改查操作。


1.尾部添加新节点


思路分析

  1. 首先要定义一个节点类,每个节点存放俩部分内容,数据域指针域
  2. 节点的数据域部分存放人物编号、姓名、外号。节点的指针域指向下一个节点。
  3. 编写构造器初始化数据域内容,为了显示方便,需要重写toString方法
  4. 定义单向链表类,初始化一个头节点,整型数据初始化为0,字符类型数据初始化为空字符。注意:头节点不存放数据,作用就是表示单链表的头。
  5. 定义节点添加的方法,思路:(1)找到当前链表的最后节点,注意先判断链表是否为空。(2)将最后的节点next指向新节点,完成尾部添加。
  6. 定义显示链表方法,通过遍历输出节点的数据域。

代码实现

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.add(hero1);singleLinkedList.add(hero2);singleLinkedList.add(hero3);singleLinkedList.add(hero4);//显示singleLinkedList.list();}
}//定义SingleLinkedList,管理英雄
class SingleLinkedList {//先初始化一个头节点,头节点不要动private HeroNode head = new HeroNode(0, "", "");//添加节点到单向链表//思路:当不考虑编号顺序时//1. 找到当前链表最后的节点//2. 将最后的节点next指向新的节点public void add(HeroNode heroNode) {//因为head节点不能动,因此我们需要一个辅助变量 tempHeroNode temp = head;//遍历链表,找到最后while (true) {//当找到链表的最后时,temp.next==nullif (temp.next == null) {break;}temp = temp.next;   //如果没有找到最后,将temp后移}//当推出while循环时,temp就指向了链表的最后,然后让它指向新的节点。temp.next = heroNode;}//显示链表【遍历】public void list() {//判断是否为空if (head.next == null) {System.out.println("链表为空!!!");return;}//因为头节点不可以动,因此需要一个辅助变量来遍历HeroNode temp = head.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 No, String Name, String Nickname) {this.no = No;this.name = Name;this.nickname = Nickname;}// 为了显示方便,重写 toString 方法@Overridepublic 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

注意事项

  • 1.由于头节点不能动,所以在遍历的时候需要一个辅助变量,或者说是辅助指针,进行指针移动。
  • 2.注意代码中俩次遍历链表时辅助指针的初始化区别。第一次遍历是在添加节点时找到链表最后节点,辅助指针是直接将head节点指针赋给它,是因为在最后节点的指针指向新节点之前,必须将先将该指针指向空;第二次遍历是在显示链表各个节点数据域的时候,而这次辅助指针的初始化和第一次不一样,是直接将头节点的下一个节点赋值给辅助指针,原因是头节点无数据域,所以显示数据需从含有第一个数据域的节点开始。
  • 3.由于在代码中想要将节点的数据域输出,所以在节点类当要重写内置的toString方法。


2.按照编号插入新节点


思路分析

  1. 首先要定义一个节点类,每个节点存放俩部分内容,数据域指针域
  2. 节点的数据域部分存放人物编号、姓名、外号。节点的指针域指向下一个节点。
  3. 编写构造器初始化数据域内容,为了显示方便,需要重写toString方法
  4. 定义单向链表类,初始化一个头节点,整型数据初始化为0,字符类型数据初始化为空字符。注意:头节点不存放数据,作用就是表示单链表的头。通过辅助指针找到新添加节点的位置,
  5. 定义节点插入的方法,使用辅助指针找到要插入位置的前一个位置,然后将新结点指向该位置的下一个节点,该位置的指针域指向新节点。
  6. 定义显示链表方法,通过遍历输出节点的数据域。

代码实现

代码和上述尾部添加法一样,只是多写了一个插入的方法。

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.addByOrder(hero1);singleLinkedList.addByOrder(hero4);singleLinkedList.addByOrder(hero2);singleLinkedList.addByOrder(hero3);singleLinkedList.addByOrder(hero3);//显示singleLinkedList.list();}
}//定义SingleLinkedList,管理英雄
class SingleLinkedList {//先初始化一个头节点,头节点不要动private HeroNode head = new HeroNode(0, "", "");//添加节点到单向链表//思路:当不考虑编号顺序时//1. 找到当前链表最后的节点//2. 将最后的节点next指向新的节点public void add(HeroNode heroNode) {//因为head节点不能动,因此我们需要一个辅助变量 tempHeroNode temp = head;//遍历链表,找到最后while (true) {//当找到链表的最后时,temp.next==nullif (temp.next == null) {break;}temp = temp.next;   //如果没有找到最后,将temp后移}//当推出while循环时,temp就指向了链表的最后,然后让它指向新的节点。temp.next = heroNode;}//第二种方式添加英雄,根据排名编号插入(如果有这个排名,则添加失败,并给出提示)public void addByOrder(HeroNode heroNode) {//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置//因为这是一个单链表,因此找的temp是位于添加位置的前一个节点,否则无法插入HeroNode temp = head;boolean flag = false;   //flag标识添加的编号是否存在,默认为falsewhile (true) {if (temp.next == null) {   //说明temp已经在链表最后break;}if (temp.next.no > heroNode.no) {   //位置找到,在temp后面插入break;} else if (temp.next.no == heroNode.no) {//说明希望添加的hero编号已经存在flag = true;   //说明编号存在break;}temp = temp.next;}//判断flag的值if (flag) {//不能添加,说明编号存在System.out.printf("准备插入的英雄的编号 %d 已经存在,不能加入\n", heroNode.no);} else {//插入到链表中,temp的后面heroNode.next = temp.next;temp.next = heroNode;}}//显示链表【遍历】public void list() {//判断是否为空if (head.next == null) {System.out.println("链表为空!!!");return;}//因为头节点不可以动,因此需要一个辅助变量来遍历HeroNode temp = head.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 No, String Name, String Nickname) {this.no = No;this.name = Name;this.nickname = Nickname;}// 为了显示方便,重写 toString 方法@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickname='" + nickname + '\'' +'}';}

运行结果:

准备插入的英雄的编号 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

注意事项

要注意的地方则是新添加的插入方法。

  • 1.注意辅助指针要找的位置,必须是所要插入地方的前一个位置,如果辅助指针指向后一个位置,那么下一步操作则是新节点指向后面这个位置,再下一步操作,发现并没有前驱节点无法指向新节点,“链子”也就断了。
  • 2.另外需要注意的一个地方是,在代码中,是以编号的形式进行排序插入,所以得知道原先链表是否已经有了该编号的数据,所以得先判断,正如代码中设立flag一举动。
  • 3.在辅助指针循环遍历判断完成之后,要将新节点指向后一个节点,而且该位置前一个节点需指向新节点,以连接好链表。


3.按照编号修改节点信息


思路分析

在修改节点信息update方法中:同样第一步先判断链表是否位空,然后借助辅助节点进行遍历查询,找到要修改的节点位置,通过flag判断是否是要修改的节点,最后进行信息更新。

新增的修改节点方法如下图:

主函数中新增的代码测试部分如下图:

代码演示

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.add(hero1);
//        singleLinkedList.add(hero2);
//        singleLinkedList.add(hero3);
//        singleLinkedList.add(hero4);//插入节点,按照编号的顺序singleLinkedList.addByOrder(hero1);singleLinkedList.addByOrder(hero4);singleLinkedList.addByOrder(hero2);singleLinkedList.addByOrder(hero3);
//        singleLinkedList.addByOrder(hero3);//显示System.out.println("未修改的链表信息如下:");singleLinkedList.list();//测试修改节点的代码HeroNode newHeroNode = new HeroNode(2, "花木兰", "麒麟志");singleLinkedList.update(newHeroNode);System.out.println("修改之后链表的信息如下:");singleLinkedList.list();}
}//定义SingleLinkedList,管理英雄
class SingleLinkedList {//先初始化一个头节点,头节点不要动private HeroNode head = new HeroNode(0, "", "");//添加节点到单向链表//思路:当不考虑编号顺序时//1. 找到当前链表最后的节点//2. 将最后的节点next指向新的节点public void add(HeroNode heroNode) {//因为head节点不能动,因此我们需要一个辅助变量 tempHeroNode temp = head;//遍历链表,找到最后while (true) {//当找到链表的最后时,temp.next==nullif (temp.next == null) {break;}temp = temp.next;   //如果没有找到最后,将temp后移}//当推出while循环时,temp就指向了链表的最后,然后让它指向新的节点。temp.next = heroNode;}//第二种方式添加英雄,根据排名编号插入(如果有这个排名,则添加失败,并给出提示)public void addByOrder(HeroNode heroNode) {//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置//因为这是一个单链表,因此找的temp是位于添加位置的前一个节点,否则无法插入HeroNode temp = head;boolean flag = false;   //flag标识添加的编号是否存在,默认为falsewhile (true) {if (temp.next == null) {   //说明temp已经在链表最后break;}if (temp.next.no > heroNode.no) {   //位置找到,在temp后面插入break;} else if (temp.next.no == heroNode.no) {//说明希望添加的hero编号已经存在flag = true;   //说明编号存在break;}temp = temp.next;}//判断flag的值if (flag) {//不能添加,说明编号存在System.out.printf("准备插入的英雄的编号 %d 已经存在,不能加入\n", heroNode.no);} else {//插入到链表中,temp的后面heroNode.next = temp.next;temp.next = heroNode;}}//修改节点的信息,根据no编号来修改,即no编号不能修改,如果编号修改,则可视为添加节点操作。//根据 newHeroNode 的 no编号 来修改即可public void update(HeroNode newHeroNode) {//判断是否为空if (head.next == null) {System.out.println("链表为空!!!");return;}//通过辅助指针找到需要修改的节点HeroNode temp = head.next;Boolean flag = false;   //表示是否找到该节点while (true) {if (temp == null) { //表示遍历链表完了,到最后了。break;}if (temp.no == newHeroNode.no) {    //已经找到索要修改的编号位置节点flag = true;break;}temp = temp.next;}//根据flag判断是否找到要修改的节点if (flag) {temp.name = newHeroNode.name;temp.nickname = newHeroNode.nickname;} else { //没有找到该节点的情况System.out.printf("没有找到编号为%d的节点\n", newHeroNode.no);}}//显示链表【遍历】public void list() {//判断是否为空if (head.next == null) {System.out.println("链表为空!!!");return;}//因为头节点不可以动,因此需要一个辅助变量来遍历HeroNode temp = head.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 No, String Name, String Nickname) {this.no = No;this.name = Name;this.nickname = Nickname;}// 为了显示方便,重写 toString 方法@Overridepublic 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='仲夏夜之梦'}
修改之后链表的信息如下:
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


4.删除节点


思路分析


删除节点的重点:

  • 1.辅助指针所要指向的位置:待删除节点的前一个位置。
  • 2.temp.next = temp.next.next

代码演示

新增的删除节点方法如下图:

主函数中新增的测试代码如下图:

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.add(hero1);
//        singleLinkedList.add(hero2);
//        singleLinkedList.add(hero3);
//        singleLinkedList.add(hero4);//插入节点,按照编号的顺序singleLinkedList.addByOrder(hero1);singleLinkedList.addByOrder(hero4);singleLinkedList.addByOrder(hero2);singleLinkedList.addByOrder(hero3);
//        singleLinkedList.addByOrder(hero3);//显示System.out.println("未修改的链表信息如下:");singleLinkedList.list();//测试修改节点的代码HeroNode newHeroNode = new HeroNode(2, "花木兰", "麒麟志");singleLinkedList.update(newHeroNode);System.out.println("修改之后链表的信息如下:");singleLinkedList.list();//删除节点singleLinkedList.del(1);singleLinkedList.del(4);singleLinkedList.del(2);singleLinkedList.del(3);System.out.println("删除第一个节点后链表的信息如下:");singleLinkedList.list();}
}//定义SingleLinkedList,管理英雄
class SingleLinkedList {//先初始化一个头节点,头节点不要动private HeroNode head = new HeroNode(0, "", "");//添加节点到单向链表//思路:当不考虑编号顺序时//1. 找到当前链表最后的节点//2. 将最后的节点next指向新的节点public void add(HeroNode heroNode) {//因为head节点不能动,因此我们需要一个辅助变量 tempHeroNode temp = head;//遍历链表,找到最后while (true) {//当找到链表的最后时,temp.next==nullif (temp.next == null) {break;}temp = temp.next;   //如果没有找到最后,将temp后移}//当推出while循环时,temp就指向了链表的最后,然后让它指向新的节点。temp.next = heroNode;}//第二种方式添加英雄,根据排名编号插入(如果有这个排名,则添加失败,并给出提示)public void addByOrder(HeroNode heroNode) {//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置//因为这是一个单链表,因此找的temp是位于添加位置的前一个节点,否则无法插入HeroNode temp = head;boolean flag = false;   //flag标识添加的编号是否存在,默认为falsewhile (true) {if (temp.next == null) {   //说明temp已经在链表最后break;}if (temp.next.no > heroNode.no) {   //位置找到,在temp后面插入break;} else if (temp.next.no == heroNode.no) {//说明希望添加的hero编号已经存在flag = true;   //说明编号存在break;}temp = temp.next;}//判断flag的值if (flag) {//不能添加,说明编号存在System.out.printf("准备插入的英雄的编号 %d 已经存在,不能加入\n", heroNode.no);} else {//插入到链表中,temp的后面heroNode.next = temp.next;temp.next = heroNode;}}//修改节点的信息,根据no编号来修改,即no编号不能修改,如果编号修改,则可视为添加节点操作。//根据 newHeroNode 的 no编号 来修改即可public void update(HeroNode newHeroNode) {//判断是否为空if (head.next == null) {System.out.println("链表为空!!!");return;}//通过辅助指针找到需要修改的节点HeroNode temp = head.next;Boolean flag = false;   //表示是否找到该节点while (true) {if (temp == null) { //表示遍历链表完了,到最后了。break;}if (temp.no == newHeroNode.no) {    //已经找到索要修改的编号位置节点flag = true;break;}temp = temp.next;}//根据flag判断是否找到要修改的节点if (flag) {temp.name = newHeroNode.name;temp.nickname = newHeroNode.nickname;} else { //没有找到该节点的情况System.out.printf("没有找到编号为%d的节点\n", newHeroNode.no);}}//删除节点的代码//思路://1. head 节点不能动,还是需要一个temp辅助节点找到待删除节点的前一个节点。//2. 在比较时,是让temp.next.no 和  需要删除的节点.no 进行比较public void del(int no) {HeroNode temp = head;Boolean flag = false;   //表示是否找到待删除节点的前一个节点while (true) {if (temp.next == null) {    //已经遍历到最后了break;}if (temp.next.no == no) {   //找到要删除节点的前一个节点编号flag = true;break;}temp = temp.next;}if (flag) { //找到,可以删除了temp.next = temp.next.next;} else {System.out.printf("要删除的节点%d不存在\n", no);}}//显示链表【遍历】public void list() {//判断是否为空if (head.next == null) {System.out.println("链表为空!!!");return;}//因为头节点不可以动,因此需要一个辅助变量来遍历HeroNode temp = head.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 No, String Name, String Nickname) {this.no = No;this.name = Name;this.nickname = Nickname;}// 为了显示方便,重写 toString 方法@Overridepublic 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='仲夏夜之梦'}
修改之后链表的信息如下:
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

注意事项

  • 1.由于是单链表,通过辅助指针要找的是被删除节点的前一个节点,否则无法删除节点。
  • 2.被删除的节点,没有任何引用会指向它,会被Java的垃圾回收机制回收掉。


常用对链表的操作


1.求单链表中有效节点的个数

思路简单,在上述代码的基础上,直接遍历,计数器+1,就OK。需要注意的地方则是,该方法应先判断链表是否为空,如若位空,直接返回0值。

代码演示

添加一个得到节点个数的方法,如下:

    public static int getLength(HeroNode head) {if (head.next == null) {return 0;}int length = 0;//定义一个辅助变量HeroNode cur = head.next;while (cur != null) {length++;cur = cur.next;}return length;}

在链表类当中,添加返回头节点(Getter一波)的方法,是因为链表类中初始化的头节点是私有的。如下图:

主函数中的测试代码如下图:

运行结果如下图:



2.查找单链表中的倒数第k个节点


思路分析

  1. 编写一个方法,接受head节点,同时接受一个index。
  2. index表示的是倒数第index个节点。
  3. 先把链表从头到尾遍历一遍,得到链表的总的长度 getlength。
  4. 得到size后,从链表的第一个开始遍历(size-index)个,就可以得到。
  5. 如果找到了,则返回该节点,否则返回空null。

代码演示

注意这部分代码调用到了上面第一部分中所写的代码,所有内容完整的代码在本文最后。

    public static HeroNode findLastIndexNode(HeroNode head, int index) {//判断如果链表为空,返回空nullif (head.next == null) {return null;    //表示没有找到}//第一次遍历得到链表的长度(节点个数)int size = getLength(head);//第二次遍历 size-index 位置,就是我们倒数的第k个节点。//先做一个index校验if (index <= 0 || index > size) {return null;}//定义一个辅助指针,for循环定位到倒数的index个HeroNode cur = head.next;for (int i = 0; i < size-index; i++) {cur = cur.next;}return cur;}

主函数中的测试代码如下图:

运行结果如下图:


注意事项

  • 1.获取链表长度的方法中,要将头节点传入该方法,是因为要通过头指针定义辅助指针,原因是因为该方法卸载了链表类的外部,所以要传入。
  • 2.在获取倒数第k个节点时,进行了俩次遍历,第一次遍历是为了获取整个链表的长度,然后再减去倒数第index个节点,就是我们要找到的节点,所以第二次遍历是则是遍历至 size-index 。
  • 3.还有一点注意的地方则是传入index值是否合理,所以提前做一个校验。


3.单链表的反转

思路分析

反转效果图

思路:

  • 1.先定义一个节点 reveerseHead = new HeroNode();
  • 2.从头到位遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端。
  • 3.原来的链表的 head.next = reverseHead.next

代码演示

    /*将单链表进行翻转【腾讯面试题】*/public static void reverseList(HeroNode head) {//如果当前链表为空,或者只有一个节点,无需反转,直接返回if (head.next == null || head.next.next == null) {return;}//定义一个辅助指针(变量),帮助我们遍历原来的链表HeroNode cur = head.next;HeroNode next = null; //指向当前节点[cur]的下一个节点HeroNode resverseHead = new HeroNode(0, "", "");//从头遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端while (cur != null) {next = cur.next; //暂时保存当前节点的下一个节点,因为后面需要使用cur.next = resverseHead.next; //将cur的下一个节点指向新的链表的最前端resverseHead.next = cur; //将cur连接到新的链表上cur = next; //让cur后移}//将head.next 指向 reverseHead.next,实现单链表的翻转head.next = resverseHead.next;}

主函数测试代码:

运行结果:

原来链表的情况~~~~~~~
HeroNode{no=1, name='铠', nickname='绛天战甲'}
HeroNode{no=2, name='瑶妹', nickname='遇见神鹿'}
HeroNode{no=3, name='吕布', nickname='天魔缭乱'}
HeroNode{no=4, name='貂蝉', nickname='仲夏夜之梦'}
翻转单链表~~~~~~~~~
HeroNode{no=4, name='貂蝉', nickname='仲夏夜之梦'}
HeroNode{no=3, name='吕布', nickname='天魔缭乱'}
HeroNode{no=2, name='瑶妹', nickname='遇见神鹿'}
HeroNode{no=1, name='铠', nickname='绛天战甲'}Process finished with exit code 0


完整Demo

这里是上文所阐述针对单链表操作的完整代码。上文中所写的代码有重复的地方,是为了查看运行结果,方便核对验证。

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.add(hero1);singleLinkedList.add(hero2);singleLinkedList.add(hero3);singleLinkedList.add(hero4);//测试一下单链表的反转功能System.out.println("原来链表的情况~~~~~~~");singleLinkedList.list();System.out.println("翻转单链表~~~~~~~~~");reverseList(singleLinkedList.getHead());singleLinkedList.list();/*//插入节点,按照编号的顺序singleLinkedList.addByOrder(hero1);singleLinkedList.addByOrder(hero4);singleLinkedList.addByOrder(hero2);singleLinkedList.addByOrder(hero3);
//        singleLinkedList.addByOrder(hero3);//显示System.out.println("未修改的链表信息如下:");singleLinkedList.list();//测试修改节点的代码HeroNode newHeroNode = new HeroNode(2, "花木兰", "麒麟志");singleLinkedList.update(newHeroNode);System.out.println("修改之后链表的信息如下:");singleLinkedList.list();//删除节点singleLinkedList.del(4);System.out.println("删除第一个节点后链表的信息如下:");singleLinkedList.list();//测试得到链表节点个数System.out.println("有效的节点个数为:" + getLength(singleLinkedList.getHead()));//测试倒数第k个节点HeroNode result = findLastIndexNode(singleLinkedList.getHead(), 2);System.out.println("res为:" + result);*/}/*将单链表进行翻转【腾讯面试题】*/public static void reverseList(HeroNode head) {//如果当前链表为空,或者只有一个节点,无需反转,直接返回if (head.next == null || head.next.next == null) {return;}//定义一个辅助指针(变量),帮助我们遍历原来的链表HeroNode cur = head.next;HeroNode next = null; //指向当前节点[cur]的下一个节点HeroNode resverseHead = new HeroNode(0, "", "");//从头遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端while (cur != null) {next = cur.next; //暂时保存当前节点的下一个节点,因为后面需要使用cur.next = resverseHead.next; //将cur的下一个节点指向新的链表的最前端resverseHead.next = cur; //将cur连接到新的链表上cur = next; //让cur后移}//将head.next 指向 reverseHead.next,实现单链表的翻转head.next = resverseHead.next;}/*查找单链表中的倒数第k个节点【新浪面试题】思路:1.编写一个方法,接受head节点,同时接受一个index2.index表示的是倒数第index个节点3.先把链表从头到尾遍历一遍,得到链表的总的长度 getlength4.得到size后,从链表的第一个开始遍历(size-index)个,就可以得到5.如果找到了,则返回该节点,否则返回空null*/public static HeroNode findLastIndexNode(HeroNode head, int index) {//判断如果链表为空,返回空nullif (head.next == null) {return null;    //表示没有找到}//第一次遍历得到链表的长度(节点个数)int size = getLength(head);//第二次遍历 size-index 位置,就是我们倒数的第k个节点。//先做一个index校验if (index <= 0 || index > size) {return null;}//定义一个辅助指针,for循环定位到倒数的index个HeroNode cur = head.next;for (int i = 0; i < size-index; i++) {cur = cur.next;}return cur;}//定义获取链表长度的方法/*head:链表的头节点返回的就是有效节点的个数*/public static int getLength(HeroNode head) {if (head.next == null) {return 0;}int length = 0;//定义一个辅助变量HeroNode cur = head.next;while (cur != null) {length++;cur = cur.next;}return length;}}//定义SingleLinkedList,管理英雄
class SingleLinkedList {//先初始化一个头节点,头节点不要动private HeroNode head = new HeroNode(0, "", "");//返回头节点public HeroNode getHead() {return head;}//添加节点到单向链表//思路:当不考虑编号顺序时//1. 找到当前链表最后的节点//2. 将最后的节点next指向新的节点public void add(HeroNode heroNode) {//因为head节点不能动,因此我们需要一个辅助变量 tempHeroNode temp = head;//遍历链表,找到最后while (true) {//当找到链表的最后时,temp.next==nullif (temp.next == null) {break;}temp = temp.next;   //如果没有找到最后,将temp后移}//当推出while循环时,temp就指向了链表的最后,然后让它指向新的节点。temp.next = heroNode;}//第二种方式添加英雄,根据排名编号插入(如果有这个排名,则添加失败,并给出提示)public void addByOrder(HeroNode heroNode) {//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置//因为这是一个单链表,因此找的temp是位于添加位置的前一个节点,否则无法插入HeroNode temp = head;boolean flag = false;   //flag标识添加的编号是否存在,默认为falsewhile (true) {if (temp.next == null) {   //说明temp已经在链表最后break;}if (temp.next.no > heroNode.no) {   //位置找到,在temp后面插入break;} else if (temp.next.no == heroNode.no) {//说明希望添加的hero编号已经存在flag = true;   //说明编号存在break;}temp = temp.next;}//判断flag的值if (flag) {//不能添加,说明编号存在System.out.printf("准备插入的英雄的编号 %d 已经存在,不能加入\n", heroNode.no);} else {//插入到链表中,temp的后面heroNode.next = temp.next;temp.next = heroNode;}}//修改节点的信息,根据no编号来修改,即no编号不能修改,如果编号修改,则可视为添加节点操作。//根据 newHeroNode 的 no编号 来修改即可public void update(HeroNode newHeroNode) {//判断是否为空if (head.next == null) {System.out.println("链表为空!!!");return;}//通过辅助指针找到需要修改的节点HeroNode temp = head.next;Boolean flag = false;   //表示是否找到该节点while (true) {if (temp == null) { //表示遍历链表完了,到最后了。break;}if (temp.no == newHeroNode.no) {    //已经找到索要修改的编号位置节点flag = true;break;}temp = temp.next;}//根据flag判断是否找到要修改的节点if (flag) {temp.name = newHeroNode.name;temp.nickname = newHeroNode.nickname;} else { //没有找到该节点的情况System.out.printf("没有找到编号为%d的节点\n", newHeroNode.no);}}//删除节点的代码//思路://1. head 节点不能动,还是需要一个temp辅助节点找到待删除节点的前一个节点。//2. 在比较时,是让temp.next.no 和  需要删除的节点.no 进行比较public void del(int no) {HeroNode temp = head;Boolean flag = false;   //表示是否找到待删除节点的前一个节点while (true) {if (temp.next == null) {    //已经遍历到最后了break;}if (temp.next.no == no) {   //找到要删除节点的前一个节点编号flag = true;break;}temp = temp.next;}if (flag) { //找到,可以删除了temp.next = temp.next.next;} else {System.out.printf("要删除的节点%d不存在\n", no);}}//显示链表【遍历】public void list() {//判断是否为空if (head.next == null) {System.out.println("链表为空!!!");return;}//因为头节点不可以动,因此需要一个辅助变量来遍历HeroNode temp = head.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 No, String Name, String Nickname) {this.no = No;this.name = Name;this.nickname = Nickname;}// 为了显示方便,重写 toString 方法@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickname='" + nickname + '\'' +'}';}
}

数据结构,单链表讲解,并使用Java代码实现单链表增删改查【尾部添加,中间插入、修改节点、删除节点、展示链表】相关推荐

  1. 双表查询java代码_多表增删改查

    [java]代码库package com.ww.service; import java.lang.reflect.Array; import java.sql.Connection; import ...

  2. java代码LDAP操作(增删改查)AD(活动目录)- 修改

    修改用户 连接LDAP的类 package cn.com.wilcom.main;import java.util.Hashtable;import javax.naming.Context; imp ...

  3. java对数据库的增删改查_在java中对数据库进行增删改查

    代码区域: package com.oracle.jdbc.demo1; import java.sql.Connection; import java.sql.DriverManager; impo ...

  4. 数据库实验IDEA编程Java程序实现连接数据库以及增删改查JDBC

    IDEA编程Java程序实现连接数据库以及增删改查JDBC IDEA的mysql环境配置建议参考该博客:戳我 我用的是java11和此博客的配置略有出入,不过一般的问题都可以百度解决 这是我实验用的数 ...

  5. java对xml文件做增删改查------摘录

    java对xml文件做增删改查 package com.wss; import java.io.File; import java.util.ArrayList; import java.util.L ...

  6. ElasticSearch初体验之使用Java进行最基本的增删改查

    好久没写博文了, 最近项目中使用到了ElaticSearch相关的一些内容, 刚好自己也来做个总结. 现在自己也只能算得上入门, 总结下自己在工作中使用Java操作ES的一些小经验吧. 本文总共分为三 ...

  7. Java操作Mongodb数据(增删改查聚合查询)

    文章目录 一.Java操作MongoDB 二.使用步骤 1.基础配置 2.实体类 3.MongoDB表数据 3.增删改查聚合查询 总结 一.Java操作MongoDB 上一篇文章介绍了,如何在本地使用 ...

  8. Java Web(十) JDBC的增删改查,C3P0等连接池,dbutils框架的使用

    前面做了一个非常垃圾的小demo,真的无法直面它,菜的抠脚啊,真的菜,好好努力把.菜鸡. --WZY 一.JDBC是什么? Java Data Base Connectivity,java数据库连接, ...

  9. Java原始客户端操作Mongodb 增删改查

    Document方式操作增删改查 1.导入Pom依赖 2.java客户端代码 1.导入Pom依赖 <dependency><groupId>org.mongodb</gr ...

  10. Java项目——模拟电话薄联系人增删改查

    该项目模拟了电话本记录联系人的业务功能,用来练习对数据库的增删改查等操作. 菜单类:Menu -- 用来封装主菜单和个选项的子菜单 Person类: Person--联系人的实体类 TelNoteRe ...

最新文章

  1. 某日是当前年的第几天
  2. python什么时候进入中国-python什么时候发明的
  3. python绘制三维散点图-python 画三维图像 曲面图和散点图的示例
  4. 第二期冲刺站立会议个人博客15(2016/6/08)
  5. android 帧动画的使用
  6. Python将JSON格式数据转换为SQL语句以便导入MySQL数据库
  7. 智能会议系统(16)---LinphoneService
  8. 共享单车变“私有”、被毁、被盗:用户们都看不下去了,举报!
  9. python服务器搭建nginx_python服务器环境搭建Flask,uwsgi和nginx
  10. Ubuntu18.10与windows7文件夹共享
  11. Module-Zero之组织单元(OU)管理【新增】
  12. Vijos P1409 纪念品分组【贪心】
  13. 结合Android源码分析总结单例模式的几种实现方式
  14. python基础--语句
  15. oracle 逗号,查询oracle中逗号分隔字符串中所有值
  16. Excel插入图表失真(数据格式原因)修复笔记
  17. Linux服务器搭建——VMware14安装
  18. python居然能语音控制电脑壁纸切换,只需60行代码
  19. 打开窗,让阳光洒进来
  20. WinRAR 实现简单的自解压

热门文章

  1. EAS工作流审批信息查询
  2. 天下3哪个服务器人最多2020,天下3:采访2020年全国竞技赛冠军——太虚没法玩...
  3. java 图片插件_[Java教程]10款功能强大的jQuery/CSS3图片特效插件
  4. linux学习正是开始---嘎嘎嘎
  5. 18- Adaboost梯度提升树 (集成算法) (算法)
  6. 2021年美赛E题目简述(中英文)
  7. Uva 10801 最短路,搜索
  8. 赠书 | 2038年的机器人写了一本书——人类帝国的覆灭
  9. 微信新版本的几个变化
  10. PS抠图方法、技巧大集合