【Java数据结构和算法】004-链表:单向链表

目录

0、警醒自己

一、链表(Linked List)介绍

1、概述

2、链表在内存中是存储如下

3、上图小结

4、单链表(带头结点) 逻辑结构示意图

二、单链表的应用实例

1、单链表类属性内容

举例:

2、添加元素

图解:

说明:

3、遍历

4、代码演示

5、运行结果

6、插入节点

图解:

分析:

7、删除节点

图解:

分析:

8、代码演示

9、运行结果

10、关于代码演示的说明

三、单链表面试题

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

思路:

代码演示:

运行结果:

2、查找单链表中的倒数第k个节点(新浪)

个人评述:

代码演示:

运行结果:

3、单链表的反转(腾讯)

说明:

日志:

代码演示:

运行结果:

4、从尾到头打印单链表(百度)

方式自选:

思路分析:

栈的使用演示:

代码演示:

运行结果:

5、合并两个有序的单链表,合并之后的链表依然有序(课后练习题)

说明:

思路分析:

代码演示:

运行结果:


0、警醒自己

1、学习不用心,骗人又骗己;

2、学习不刻苦,纸上画老虎;

3、学习不惜时,终得人耻笑;

4、学习不复习,不如不学习;

5、学习不休息,毁眼伤身体;

6、狗才等着别人喂,狼都是自己寻找食物;

 

一、链表(Linked List)介绍

1、概述

链表是有序的列表;

 

2、链表在内存中是存储如下

 

3、上图小结

①链表是以节点的方式来存储,是链式存储

②每个节点包含 data 域(存储实际值),next 域(指向下一个节点);

③如图:发现链表的各个节点不一定是连续存储

④链表分为带头节点的链表不带头节点的链表,根据实际的需求来确定;

 

4、单链表(带头结点) 逻辑结构示意图

 

二、单链表的应用实例

1、单链表类属性内容

唯一ID + 类原始属性 + 下一个元素的ID;

(注意:我这里写的是下一个元素的ID,实际上代码演示的是下一个节点,其实本质没啥区别,但需要灵活理解!)

举例:

public class HeroNode{
    private int id;
    private String name;
    private String nickname;
    private int nextId;
}

 

2、添加元素

图解:

(备注:这里的nextId是另一种方式,代码演示中用的是next(下一个元素节点),其本质含义都是相通的,这里不再修改!)

说明:

先创建一个头节点“元素”,默认nextId为null,每添加一个元素,将上一个元素的nextId赋值为自己的id,将自己的nextId复制为null;

 

3、遍历

从头结点拿到第一个元素的id,依次通过nextId获取下一个元素的id,直到nextId为null,链表结束,停止遍历;

 

4、代码演示

(链表 = 无限套娃!)

package com.zb.ds;

//测试单向链表
public class TestLinkedList {
    public static void main(String[] args) {
        //测试单链表
        SingleLinkedList list = new SingleLinkedList();
        //添加
        list.add(new HeroNode(1,"宋江","及时雨"));
        list.add(new HeroNode(2,"林冲","豹子头"));
        list.add(new HeroNode(3,"卢俊义","玉麒麟"));
        //遍历
        list.show();
    }
}
//定义一个单链表,来管理我们的英雄
class SingleLinkedList{
    //初始化一个头节点,头节点不要动
    private final HeroNode head = new HeroNode(0,"","");
    //添加节点思路:首先找到链表的最后一个节点,然后将此节点的nextId指向新节点
    public void add(HeroNode heroNode){
        HeroNode temp = head;
        while (temp.next != null) {
            //拿到下一个节点
            temp = temp.next;
        }
        //当前while循环退出,说明拿到了next为null的节点,也就是最后一个节点
        temp.next = heroNode;
        System.out.println(heroNode.nickname + heroNode.name + "添加成功!");
    }
    //遍历输出
    public void show(){
        HeroNode temp = head;
        while (temp.next != null){
            System.out.println(temp.nickname + temp.name);
            temp = temp.next;
        }
        //上面走到最后一个节点的时候跑了,这里接着输出一下才行
        System.out.println(temp.nickname + temp.name);
    }
}
//英雄
class HeroNode{
    final int id;
    final String name;
    final String nickname;
    HeroNode next;

    public HeroNode(int id, String name, String nickname) {
        this.id = id;
        this.name = name;
        this.nickname = nickname;
    }
}

 

5、运行结果

及时雨宋江添加成功!
豹子头林冲添加成功!
玉麒麟卢俊义添加成功!

及时雨宋江
豹子头林冲
玉麒麟卢俊义

 

6、插入节点

图解:

(备注:这里的nextId是另一种方式,代码演示中用的是next(下一个元素节点),其本质含义都是相通的,这里不再修改!)

分析:

在中间插入一个id肯定是要给插入的位置的,比如将一个新的节点插入到id为2的节点之后,那么我们首先需要对链表进行遍历,找到id为2的节点,然会进行相关操作即可,详见代码演示;

 

7、删除节点

图解:

(备注:这里的nextId是另一种方式,代码演示中用的是next(下一个元素节点),其本质含义都是相通的,这里不再修改!)

分析:

要删除一个节点,肯定是要给要删除节点的id,然后进行遍历查找,在进行相关操作即可,详见代码演示;

 

8、代码演示

(在上面的代码基础上添加)

package com.zb.ds;

//测试单向链表
public class TestLinkedList {
    public static void main(String[] args) {
        //测试单链表
        SingleLinkedList list = new SingleLinkedList();
        //添加
        list.add(new HeroNode(1,"宋江","及时雨"));
        list.add(new HeroNode(2,"林冲","豹子头"));
        list.add(new HeroNode(3,"卢俊义","玉麒麟"));
        list.insert(2,new HeroNode(4,"林冲之后","豹子头林冲之后"));
        list.remove(2);
        //遍历
        list.show();
    }
}
//定义一个单链表,来管理我们的英雄
class SingleLinkedList{
    //初始化一个头节点,头节点不要动
    private final HeroNode head = new HeroNode(0,"","");

    //添加节点思路:首先找到链表的最后一个节点,然后将此节点的nextId指向新节点
    public void add(HeroNode heroNode){
        HeroNode temp = head;
        while (temp.next != null) {
            //拿到下一个节点
            temp = temp.next;
        }
        //当前while循环退出,说明拿到了next为null的节点,也就是最后一个节点
        temp.next = heroNode;
        System.out.println(heroNode.nickname + heroNode.name + "添加成功!");
    }

    //指定一个节点的id,在其后面添加一个节点
    public void insert(int id, HeroNode heroNode){
        HeroNode temp = head;
        while (temp.id != id) {
            //拿到下一个节点
            temp = temp.next;
        }
        //跳出循环之后,就代表找到了给定的id
        HeroNode t = temp.next;
        temp.next = heroNode;
        heroNode.next = t;
        System.out.println(heroNode.nickname + heroNode.name + "添加成功!");
    }

    //删除指定id的节点之后的那个节点
    //为什么不删除指定id的节点?因为单向链表只知道后面是谁,不知道前面是谁,也可以删除,
    // 但是需要再进行一次遍历,这里演示的是如何删除一个节点,所以不做过多纠结!
    public void remove(int id){
        HeroNode temp = head;
        while (temp.id != id) {
            //拿到下一个节点
            temp = temp.next;
        }
        System.out.println("移除了" + temp.next.nickname + temp.next.name);
        //这个时候,temp是要被删除的节点前面的那个节点
        temp.next = temp.next.next;
    }

    //遍历输出
    public void show(){
        HeroNode temp = head;
        while (temp.next != null){
            System.out.println(temp.nickname + temp.name);
            temp = temp.next;
        }
        //上面走到最后一个节点的时候跑了,这里接着输出一下才行
        System.out.println(temp.nickname + temp.name);
    }
}

//英雄
class HeroNode{
    final int id;
    final String name;
    final String nickname;
    HeroNode next;

    public HeroNode(int id, String name, String nickname) {
        this.id = id;
        this.name = name;
        this.nickname = nickname;
    }
}

 

9、运行结果

及时雨宋江添加成功!
豹子头林冲添加成功!
玉麒麟卢俊义添加成功!
豹子头林冲之后林冲之后添加成功!
移除了豹子头林冲之后林冲之后

及时雨宋江
豹子头林冲
玉麒麟卢俊义

 

10、关于代码演示的说明

这里我们假定所给定的id是存在的,且所给定的新英雄的id不和其他id重复,否则再循环遍历的时候需要判断temp.next为空的、id已经存在,给定的id不存在、插入失败等情况;

我们也可以修改某一节点的内容,需要遍历找到给定id对应的节点,再进行修改操作即可,与上面的代码区别不大,不再具体演示;

 

三、单链表面试题

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

思路:

定义一个变量count=0,用来记录有效节点的个数;

直接遍历,每遍历一个count++,知道next==null,此时count即为有效节点的个数;

 

 

代码演示:

(在上面的基础上)

package com.zb.ds;

//测试单向链表
public class TestLinkedList {
    public static void main(String[] args) {
        //测试单链表
        SingleLinkedList list = new SingleLinkedList();
        //添加
        list.add(new HeroNode(1,"宋江","及时雨"));
        list.add(new HeroNode(2,"林冲","豹子头"));
        list.add(new HeroNode(3,"卢俊义","玉麒麟"));
        list.insert(2,new HeroNode(4,"林冲之后","豹子头林冲之后"));
        list.remove(2);
        //遍历
        list.show();
        //输出有效节点个数
        System.out.println("有效节点个数:" + list.getNum());
    }
}
//定义一个单链表,来管理我们的英雄
class SingleLinkedList{
    //初始化一个头节点,头节点不要动
    private final HeroNode head = new HeroNode(0,"","");

    //添加节点思路:首先找到链表的最后一个节点,然后将此节点的nextId指向新节点
    public void add(HeroNode heroNode){
        HeroNode temp = head;
        while (temp.next != null) {
            //拿到下一个节点
            temp = temp.next;
        }
        //当前while循环退出,说明拿到了next为null的节点,也就是最后一个节点
        temp.next = heroNode;
        System.out.println(heroNode.nickname + heroNode.name + "添加成功!");
    }

    //指定一个节点的id,在其后面添加一个节点
    public void insert(int id, HeroNode heroNode){
        HeroNode temp = head;
        while (temp.id != id) {
            //拿到下一个节点
            temp = temp.next;
        }
        //跳出循环之后,就代表找到了给定的id
        HeroNode t = temp.next;
        temp.next = heroNode;
        heroNode.next = t;
        System.out.println(heroNode.nickname + heroNode.name + "添加成功!");
    }

    //删除指定id的节点之后的那个节点
    //为什么不删除指定id的节点?因为单向链表只知道后面是谁,不知道前面是谁,也可以删除,
    // 但是需要再进行一次遍历,这里演示的是如何删除一个节点,所以不做过多纠结!
    public void remove(int id){
        HeroNode temp = head;
        while (temp.id != id) {
            //拿到下一个节点
            temp = temp.next;
        }
        System.out.println("移除了" + temp.next.nickname + temp.next.name);
        //这个时候,temp是要被删除的节点前面的那个节点
        temp.next = temp.next.next;
    }

    //遍历输出
    public void show(){
        HeroNode temp = head;
        while (temp.next != null){
            System.out.println(temp.nickname + temp.name);
            temp = temp.next;
        }
        //上面走到最后一个节点的时候跑了,这里接着输出一下才行
        System.out.println(temp.nickname + temp.name);
    }

    //获取有效节点的个数
    public int getNum(){
        int count = 0;
        HeroNode temp = head;
        while (temp.next != null){
            temp = temp.next;
            count++;
        }
        return count;
    }
}

//英雄
class HeroNode{
    final int id;
    final String name;
    final String nickname;
    HeroNode next;

    public HeroNode(int id, String name, String nickname) {
        this.id = id;
        this.name = name;
        this.nickname = nickname;
    }
}

 

运行结果:

及时雨宋江添加成功!
豹子头林冲添加成功!
玉麒麟卢俊义添加成功!
豹子头林冲之后林冲之后添加成功!
移除了豹子头林冲之后林冲之后

及时雨宋江
豹子头林冲
玉麒麟卢俊义
有效节点个数:3

 

2、查找单链表中的倒数第k个节点(新浪)

个人评述:

如果没有第1题,获取有效节点个数,这个题没那么简单就想到思路,既然上一个题已经得到了有效节点的个数count,那么这个问题就转化成了查找单链表中的正数第count-k+1个节点了;

 

代码演示:

(在上面代码的基础上)

package com.zb.ds;

//测试单向链表
public class TestLinkedList {
    public static void main(String[] args) {
        //测试单链表
        SingleLinkedList list = new SingleLinkedList();
        //添加
        list.add(new HeroNode(1,"宋江","及时雨"));
        list.add(new HeroNode(2,"林冲","豹子头"));
        list.add(new HeroNode(3,"卢俊义","玉麒麟"));
        //再添加几个节点
        list.add(new HeroNode(5,"吴用","智多星"));
        list.add(new HeroNode(6,"公孙胜","入云龙"));
        list.add(new HeroNode(7,"关胜","大刀"));
        list.insert(2,new HeroNode(4,"林冲之后","豹子头林冲之后"));
        list.remove(2);
        //遍历
        list.show();
        //输出有效节点个数
        System.out.println("有效节点个数:" + list.getNum());
        //倒数第3个节点
        list.getK(3);
    }
}
//定义一个单链表,来管理我们的英雄
class SingleLinkedList{
    //初始化一个头节点,头节点不要动
    private final HeroNode head = new HeroNode(0,"","");

    //添加节点思路:首先找到链表的最后一个节点,然后将此节点的nextId指向新节点
    public void add(HeroNode heroNode){
        HeroNode temp = head;
        while (temp.next != null) {
            //拿到下一个节点
            temp = temp.next;
        }
        //当前while循环退出,说明拿到了next为null的节点,也就是最后一个节点
        temp.next = heroNode;
        System.out.println(heroNode.nickname + heroNode.name + "添加成功!");
    }

    //指定一个节点的id,在其后面添加一个节点
    public void insert(int id, HeroNode heroNode){
        HeroNode temp = head;
        while (temp.id != id) {
            //拿到下一个节点
            temp = temp.next;
        }
        //跳出循环之后,就代表找到了给定的id
        HeroNode t = temp.next;
        temp.next = heroNode;
        heroNode.next = t;
        System.out.println(heroNode.nickname + heroNode.name + "添加成功!");
    }

    //删除指定id的节点之后的那个节点
    //为什么不删除指定id的节点?因为单向链表只知道后面是谁,不知道前面是谁,也可以删除,
    // 但是需要再进行一次遍历,这里演示的是如何删除一个节点,所以不做过多纠结!
    public void remove(int id){
        HeroNode temp = head;
        while (temp.id != id) {
            //拿到下一个节点
            temp = temp.next;
        }
        System.out.println("移除了" + temp.next.nickname + temp.next.name);
        //这个时候,temp是要被删除的节点前面的那个节点
        temp.next = temp.next.next;
    }

    //遍历输出
    public void show(){
        HeroNode temp = head;
        while (temp.next != null){
            System.out.println(temp.nickname + temp.name);
            temp = temp.next;
        }
        //上面走到最后一个节点的时候跑了,这里接着输出一下才行
        System.out.println(temp.nickname + temp.name);
    }

    //获取有效节点的个数
    public int getNum(){
        int count = 0;
        HeroNode temp = head;
        while (temp.next != null){
            temp = temp.next;
            count++;
        }
        return count;
    }

    //获取倒数第k个节点
    public void getK(int k){
        int count = 0;
        int index = getNum() - k + 1;
        HeroNode temp = head;
        while (count != index){
            temp = temp.next;
            count++;
        }
        //此时的temp就是要找的节点
        System.out.println("倒数第" + k + "个节点为:" + temp.nickname + temp.name);
    }
}

//英雄
class HeroNode{
    final int id;
    final String name;
    final String nickname;
    HeroNode next;

    public HeroNode(int id, String name, String nickname) {
        this.id = id;
        this.name = name;
        this.nickname = nickname;
    }
}

 

运行结果:

及时雨宋江添加成功!
豹子头林冲添加成功!
玉麒麟卢俊义添加成功!
智多星吴用添加成功!
入云龙公孙胜添加成功!
大刀关胜添加成功!
豹子头林冲之后林冲之后添加成功!
移除了豹子头林冲之后林冲之后

及时雨宋江
豹子头林冲
玉麒麟卢俊义
智多星吴用
入云龙公孙胜
大刀关胜
有效节点个数:6
倒数第3个节点为:智多星吴用

 

3、单链表的反转(腾讯)

说明:

单转意思就是把链表的顺序颠倒过来;

 

日志:

第一次写了很久总是出错,以致于当天感到些许的沮丧,没有继续,第二天一大早,思路清晰,几分钟写出来了,看来写代码之前、做事情之前需要清晰的思路;另一方面,如果思路不清晰的话,需要先理清思路,再开始做事情;

 

代码演示:

(在之前的基础上)

package com.zb.ds;

//测试单向链表
public class TestLinkedList {
    public static void main(String[] args) {
        //测试单链表
        SingleLinkedList list = new SingleLinkedList();
        //添加
        list.add(new HeroNode(1,"宋江","及时雨"));
        list.add(new HeroNode(2,"林冲","豹子头"));
        list.add(new HeroNode(3,"卢俊义","玉麒麟"));
        //再添加几个节点
        list.add(new HeroNode(5,"吴用","智多星"));
        list.add(new HeroNode(6,"公孙胜","入云龙"));
        list.add(new HeroNode(7,"关胜","大刀"));
        list.insert(2,new HeroNode(4,"林冲之后","豹子头林冲之后"));
        list.remove(2);
        //遍历
        System.out.println();
        System.out.println("正序:");
        list.show();
        //输出有效节点个数
        System.out.println("有效节点个数:" + list.getNum());
        //倒数第3个节点
        list.getK(3);

        //反转
        System.out.println();
        System.out.println("倒序:");
        list.invertedList();
        list.show();
    }
}
//定义一个单链表,来管理我们的英雄
class SingleLinkedList{
    //初始化一个头节点,头节点不要动
    private final HeroNode head = new HeroNode(0,"","");

    //添加节点思路:首先找到链表的最后一个节点,然后将此节点的nextId指向新节点
    public void add(HeroNode heroNode){
        HeroNode temp = head;
        while (temp.next != null) {
            //拿到下一个节点
            temp = temp.next;
        }
        //当前while循环退出,说明拿到了next为null的节点,也就是最后一个节点
        temp.next = heroNode;
        System.out.println(heroNode.nickname + heroNode.name + "添加成功!");
    }

    //指定一个节点的id,在其后面添加一个节点
    public void insert(int id, HeroNode heroNode){
        HeroNode temp = head;
        while (temp.id != id) {
            //拿到下一个节点
            temp = temp.next;
        }
        //跳出循环之后,就代表找到了给定的id
        HeroNode t = temp.next;
        temp.next = heroNode;
        heroNode.next = t;
        System.out.println(heroNode.nickname + heroNode.name + "添加成功!");
    }

    //删除指定id的节点之后的那个节点
    //为什么不删除指定id的节点?因为单向链表只知道后面是谁,不知道前面是谁,也可以删除,
    // 但是需要再进行一次遍历,这里演示的是如何删除一个节点,所以不做过多纠结!
    public void remove(int id){
        HeroNode temp = head;
        while (temp.id != id) {
            //拿到下一个节点
            temp = temp.next;
        }
        System.out.println("移除了" + temp.next.nickname + temp.next.name);
        //这个时候,temp是要被删除的节点前面的那个节点
        temp.next = temp.next.next;
    }

    //遍历输出
    public void show(){
        HeroNode temp = head;
        while (temp.next != null){
            System.out.println(temp.nickname + temp.name);
            temp = temp.next;
        }
        //上面走到最后一个节点的时候跑了,这里接着输出一下才行
        System.out.println(temp.nickname + temp.name);
    }

    //获取有效节点的个数
    public int getNum(){
        int count = 0;
        HeroNode temp = head;
        while (temp.next != null){
            temp = temp.next;
            count++;
        }
        return count;
    }

    //获取倒数第k个节点
    public void getK(int k){
        int count = 0;
        int index = getNum() - k + 1;
        HeroNode temp = head;
        while (count != index){
            temp = temp.next;
            count++;
        }
        //此时的temp就是要找的节点
        System.out.println("倒数第" + k + "个节点为:" + temp.nickname + temp.name);
    }

    //单链表的反转
    public void  invertedList(){
        //如果为空或者元素数量为1,就直接返回
        if(head.next==null || head.next.next==null){
            return;
        }
        //初始化一个新的头节点,头节点不要动
        HeroNode newHead = new HeroNode(0,"","");
        HeroNode next;//用来存储下一个节点,防止断链
        //当前节点
        HeroNode thisNode = head.next;//我们先拿到第一个节点
        //上一个节点,默认为空,正好将第一个元素的下一个节点设置为空,编程单链表的队尾
        HeroNode last = null;
        while (thisNode != null){//如果当前节点不为空,就进行下列操作
            //将当前节点之后的节点存储下来
            next = thisNode.next;
            //将当前节点指向上一个与头节点连接的节点
            thisNode.next = last;
            //头节点指向当前节点
            newHead.next = thisNode;
            //将上一个节点存下来,以备后面的连接进行连接
            last = thisNode;
            //将当前节点后移一个节点
            thisNode = next;
        }
        //这个时候便利了所有节点,newHead这个新的头节点后面连接的元素是原链表的反转,
        // 我们将newHead的next赋值给原链表头节点的next,即可完成最终的反转
        head.next = newHead.next;//之所以没有使得head=newHead是因为头节点不能动,要固定
    }
}

//英雄
class HeroNode{
    final int id;
    final String name;
    final String nickname;
    HeroNode next;

    public HeroNode(int id, String name, String nickname) {
        this.id = id;
        this.name = name;
        this.nickname = nickname;
    }
}

 

运行结果:

 

4、从尾到头打印单链表(百度)

方式自选:

1、反向遍历;2、Stack栈;

 

思路分析:

方式一(反转遍历打印):上面腾讯的链表反转了,再进行遍历打印即可,这种方式是不建议的,因为人家只是要求遍历打印,反转的话把链表的结构也给破坏了;

方式二:利用栈这个数据结构,将各个节点压入到栈中,利用栈先进后出的特点,实现逆序打印的效果;

 

栈的使用演示:

代码:

package com.zb.ds;

import java.util.Stack;

//演示栈的使用
public class TestStack {
    public static void main(String[] args) {
        Stack<String> stack = new Stack<>();
        //入栈
        stack.add("大哥");
        stack.add("二哥");
        stack.add("三哥");
        stack.add("四哥");
        //出栈
        while (stack.size()>0){
            System.out.println(stack.pop());//pop就是从栈顶的元素取出
        }
    }
}

运行结果(for循环遍历的话是正序的):

 

代码演示:

(在之前的基础上)

package com.zb.ds;

import java.util.Stack;

//测试单向链表
public class TestLinkedList {
    public static void main(String[] args) {
        //测试单链表
        SingleLinkedList list = new SingleLinkedList();
        //添加
        list.add(new HeroNode(1,"宋江","及时雨"));
        list.add(new HeroNode(2,"林冲","豹子头"));
        list.add(new HeroNode(3,"卢俊义","玉麒麟"));
        //再添加几个节点
        list.add(new HeroNode(5,"吴用","智多星"));
        list.add(new HeroNode(6,"公孙胜","入云龙"));
        list.add(new HeroNode(7,"关胜","大刀"));
        list.insert(2,new HeroNode(4,"林冲之后","豹子头林冲之后"));
        list.remove(2);
        //遍历
        System.out.println();
        System.out.println("正序:");
        list.show();
        //输出有效节点个数
        System.out.println("有效节点个数:" + list.getNum());
        //倒数第3个节点
        list.getK(3);

        //反转
//        System.out.println();
//        System.out.println("倒序:");
//        list.invertedList();
//        list.show();
        //逆序遍历
        //方式二:利用栈这个数据结构,将各个节点压入到栈中,利用栈先进后出的特点,实现逆序打印的效果;
        System.out.println("逆序遍历:");
        list.reversePrint();
    }
}
//定义一个单链表,来管理我们的英雄
class SingleLinkedList{
    //初始化一个头节点,头节点不要动
    private final HeroNode head = new HeroNode(0,"","");

    //添加节点思路:首先找到链表的最后一个节点,然后将此节点的nextId指向新节点
    public void add(HeroNode heroNode){
        HeroNode temp = head;
        while (temp.next != null) {
            //拿到下一个节点
            temp = temp.next;
        }
        //当前while循环退出,说明拿到了next为null的节点,也就是最后一个节点
        temp.next = heroNode;
        System.out.println(heroNode.nickname + heroNode.name + "添加成功!");
    }

    //指定一个节点的id,在其后面添加一个节点
    public void insert(int id, HeroNode heroNode){
        HeroNode temp = head;
        while (temp.id != id) {
            //拿到下一个节点
            temp = temp.next;
        }
        //跳出循环之后,就代表找到了给定的id
        HeroNode t = temp.next;
        temp.next = heroNode;
        heroNode.next = t;
        System.out.println(heroNode.nickname + heroNode.name + "添加成功!");
    }

    //删除指定id的节点之后的那个节点
    //为什么不删除指定id的节点?因为单向链表只知道后面是谁,不知道前面是谁,也可以删除,
    // 但是需要再进行一次遍历,这里演示的是如何删除一个节点,所以不做过多纠结!
    public void remove(int id){
        HeroNode temp = head;
        while (temp.id != id) {
            //拿到下一个节点
            temp = temp.next;
        }
        System.out.println("移除了" + temp.next.nickname + temp.next.name);
        //这个时候,temp是要被删除的节点前面的那个节点
        temp.next = temp.next.next;
    }

    //遍历输出
    public void show(){
        HeroNode temp = head;
        while (temp.next != null){
            System.out.println(temp.nickname + temp.name);
            temp = temp.next;
        }
        //上面走到最后一个节点的时候跑了,这里接着输出一下才行
        System.out.println(temp.nickname + temp.name);
    }

    //逆序打印
    public void reversePrint(){
        if(head.next==null){
            return;
        }
        //创建一个栈,存储数据为HeroNode,将英雄遍历压入栈
        Stack<HeroNode> stack = new Stack<>();
        //入栈
        HeroNode temp = head;
        while (temp.next != null){
            stack.add(temp.next);
            temp = temp.next;
        }
        //出栈
        while (stack.size()>0){
            HeroNode pop = stack.pop();
            System.out.println(pop.nickname + pop.name);
        }
    }

    //获取有效节点的个数
    public int getNum(){
        int count = 0;
        HeroNode temp = head;
        while (temp.next != null){
            temp = temp.next;
            count++;
        }
        return count;
    }

    //获取倒数第k个节点
    public void getK(int k){
        int count = 0;
        int index = getNum() - k + 1;
        HeroNode temp = head;
        while (count != index){
            temp = temp.next;
            count++;
        }
        //此时的temp就是要找的节点
        System.out.println("倒数第" + k + "个节点为:" + temp.nickname + temp.name);
    }

    //单链表的反转
    public void  invertedList(){
        //如果为空或者元素数量为1,就直接返回
        if(head.next==null || head.next.next==null){
            return;
        }
        //初始化一个新的头节点,头节点不要动
        HeroNode newHead = new HeroNode(0,"","");
        HeroNode next;//用来存储下一个节点,防止断链
        //当前节点
        HeroNode thisNode = head.next;//我们先拿到第一个节点
        //上一个节点,默认为空,正好将第一个元素的下一个节点设置为空,编程单链表的队尾
        HeroNode last = null;
        while (thisNode != null){//如果当前节点不为空,就进行下列操作
            //将当前节点之后的节点存储下来
            next = thisNode.next;
            //将当前节点指向上一个与头节点连接的节点
            thisNode.next = last;
            //头节点指向当前节点
            newHead.next = thisNode;
            //将上一个节点存下来,以备后面的连接进行连接
            last = thisNode;
            //将当前节点后移一个节点
            thisNode = next;
        }
        //这个时候便利了所有节点,newHead这个新的头节点后面连接的元素是原链表的反转,
        // 我们将newHead的next赋值给原链表头节点的next,即可完成最终的反转
        head.next = newHead.next;//之所以没有使得head=newHead是因为头节点不能动,要固定
    }
}

//英雄
class HeroNode{
    final int id;
    final String name;
    final String nickname;
    HeroNode next;

    public HeroNode(int id, String name, String nickname) {
        this.id = id;
        this.name = name;
        this.nickname = nickname;
    }
}

运行结果:

 

5、合并两个有序的单链表,合并之后的链表依然有序(课后练习题)

说明:

比如第一个链表内部节点的id为1、3、5、7,第二个链表内部节点的id为2,4,6,8,10,我们将这两个链表合并,要求顺序是1、2、3、4、5、6、7、8、10;

 

思路分析:

创建一个新的链表,循环遍历两个链表的所有节点,从小到大add到新链表里面(注意add的时候不要把整个剩余链表都add进去,要进行阉割,放大就是new一个节点,使用即将被add的节点各个属性值进行初始化),谁被add了就往后移一位,直到链表末尾,如果剩下的另一个链表还有节点,直接add剩下的链表所有元素(因为原来的链表就是有序的);

 

代码演示:

package com.zb.ds;

//测试单向链表
public class TestLinkedList {
    public static void main(String[] args) {
        //测试单链表
        SingleLinkedList list = new SingleLinkedList();
        HeroNode head = list.getHead();
        list.add(new HeroNode(1,"宋江","及时雨"),head);
        list.add(new HeroNode(3,"林冲","豹子头"),head);
        list.add(new HeroNode(5,"卢俊义","玉麒麟"),head);
        list.add(new HeroNode(7,"吴用","智多星"),head);
        SingleLinkedList list1 = new SingleLinkedList();
        HeroNode head1 = list1.getHead();
        list1.add(new HeroNode(2,"大哥","大哥"),head1);
        list1.add(new HeroNode(4,"二哥","二哥"),head1);
        list1.add(new HeroNode(6,"三哥","三哥"),head1);
        list1.add(new HeroNode(8,"四哥","四哥"),head1);
        list1.add(new HeroNode(10,"五弟","五弟"),head1);
        //遍历
        System.out.println("合并之前:");
        list.show();
        list1.show();
        System.out.println("合并之后:");
        SingleLinkedList.merge(list.getHead(),list1.getHead()).show();
    }
}
//定义一个单链表,来管理我们的英雄
class SingleLinkedList{
    //初始化一个头节点,头节点不要动
    private final HeroNode head = new HeroNode(0,"","");

    //添加节点思路:首先找到链表的最后一个节点,然后将此节点的nextId指向新节点
    public void add(HeroNode heroNode,HeroNode head){
        HeroNode temp = head;
        while (temp.next != null) {
            //拿到下一个节点
            temp = temp.next;
        }
        //当前while循环退出,说明拿到了next为null的节点,也就是最后一个节点
        temp.next = heroNode;
        System.out.println(heroNode.nickname + heroNode.name + "添加成功!");
    }

    //遍历输出
    public void show(){
        HeroNode temp = head;
        while (temp.next != null){
            System.out.println(temp.id + temp.nickname + temp.name);
            temp = temp.next;
        }
        //上面走到最后一个节点的时候跑了,这里接着输出一下才行
        System.out.println(temp.id + temp.nickname + temp.name);
    }

    //获取head
    public HeroNode getHead(){
        return head;
    }

    //合并两个有序的单链表,合并之后的链表依然有序
   public static SingleLinkedList merge(HeroNode head1,HeroNode head2){
       SingleLinkedList list = new SingleLinkedList();
       //分别存储两个链表剩下的节点
       HeroNode next1 = head1.next;
       HeroNode next2 = head2.next;
       //循环遍历两个链表中的所有节点
       while (next1!=null || next2!=null){
           //如果两个都没循环结束
           if(next1!=null && next2!=null){
               //拿到两个列表剩余首节点的id
               int id1 = next1.id;
               int id2 = next2.id;
               if(id1<id2){
                   //注意添加的时候,不要把后面一连串都添加进来,要进行阉割之后再添加
                   list.add(new HeroNode(next1.id,next1.name,next1.nickname),list.getHead());
                   //当添加1的时候,1需要往后走一步,2没用不需要往后走
                   next1 = next1.next;
               }else {
                   list.add(new HeroNode(next2.id,next2.name,next2.nickname),list.getHead());
                   //当添加2的时候,2需要往后走一步,1没用不需要往后走
                   next2 = next2.next;
               }
           }
           //当2为空的时候,全部按顺序添加1
           if(next1!=null && next2==null){
               //这个时候就不需要阉割了
               list.add(next1,list.getHead());
               next1 = null;
           }
           //当1位空的时候,全部按顺序添加2
           if(next1==null && next2!=null){
               list.add(next2,list.getHead());
               next2 = null;
           }
       }
       return list;
   }
}

//英雄
class HeroNode{
    final int id;
    final String name;
    final String nickname;
    HeroNode next;

    public HeroNode(int id, String name, String nickname) {
        this.id = id;
        this.name = name;
        this.nickname = nickname;
    }
}

 

运行结果:

(里面的0进行简单的优化处理即可!)

及时雨宋江添加成功!
豹子头林冲添加成功!
玉麒麟卢俊义添加成功!
智多星吴用添加成功!
大哥大哥添加成功!
二哥二哥添加成功!
三哥三哥添加成功!
四哥四哥添加成功!
五弟五弟添加成功!
合并之前:
0
1及时雨宋江
3豹子头林冲
5玉麒麟卢俊义
7智多星吴用
0
2大哥大哥
4二哥二哥
6三哥三哥
8四哥四哥
10五弟五弟
合并之后:
及时雨宋江添加成功!
大哥大哥添加成功!
豹子头林冲添加成功!
二哥二哥添加成功!
玉麒麟卢俊义添加成功!
三哥三哥添加成功!
智多星吴用添加成功!
四哥四哥添加成功!
0
1及时雨宋江
2大哥大哥
3豹子头林冲
4二哥二哥
5玉麒麟卢俊义
6三哥三哥
7智多星吴用
8四哥四哥
10五弟五弟

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值