【日题】关于躺下的数组突然跳起来殴打我这件事 - 翻转链表(下)

书接上回。

目录

1. “ListNode” 类

2. “sequenceList” 类

3. “ReverseSolution” 类

4. “main” 类

上回书说到,“ListNode” 类竟然有三个构造函数,那么应该以哪一个构造函数为默认值进行类的实例化呢?

public class ListNode {
    int val;
    ListNode next;

    ListNode() {}
    ListNode(int val) {
        this.val = val;
    }
    ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

新人也许会想一下,可老鸟们已经跳过了这一趴~~


1. “ListNode” 类

成员变量(英文为Fields 或 Attributes)

1) 该节点的值

int val;

2) 该节点的下一个节点(地址)

ListNode next;

方法(又称函数,英文为 Methods 或 functions)

没错,当没有参数输入的时候,执行:

ListNode() {}

当只有一个参数输入的时候,执行:

ListNode(int val) {
        this.val = val;
    }

当有两个参数传入的时候,执行:

ListNode(int val, ListNode next) {
    this.val = val;
    this.next = next;
}

这部分对 ListNode 的定义,是题目中给定的,那么我们在本地构建这个工程的时候,就要原封不动的把这个类平移到自己的工程中:

有的同学看到我的类有四个:

1. 一个是定义 Node 的属性的类(ListNode);

2. 一个是主函数 main(用于调用各个函数以及程序运行);

3. 一个是反转链表的类,也就是本题题解(ReverseSolution);

4. 最后一个,便是“制造链表”的类,sequanceList;

第一个类 “ListNode” 我们已经知晓其构造及用途,另外是三个类我会在下面一一进行解释——


2. “squanceList” 类

public class sequanceList {
    private ListNode head = null;
    private ListNode current = null;

    public void add(int value){
        ListNode newNode = new ListNode(value);

        if (head == null){
            head = newNode;
            current = newNode;
        }
        else {
            current.next = newNode;
            current = newNode;
        }
    }
    public void display(ListNode list){
        ListNode temp = list;
        while (temp != null){
            int value = temp.val;
            System.out.println("value: "+ value);
            temp = temp.next;
        }
    }

    public ListNode SeqHead(){
        return head;
    }
}

成员变量

这个类的内部,我们定义了 2 个成员变量(fields):“head” “current”

这两个变量(又称参数)的类型都是 ListNode 类型,也就是题目中要求的节点类型,也就是两个节点,从名字可以看出,“head” 是头节点。

根据我们前一篇文章所述,只要获得链表的头节点,就等于获取了整个链表,因此将它作为成员变量是很有必要的。

另一个是 “current” 节点,在这里可以近似理解为类似 C 语言中的指针。由于 Java 是“面向对象”的语言,因此并不明确存在“指针” 类型的操作,因此以对象作为指针,用来标记 “当前指向的对象(的地址)”

方法

这个类一共设置了三个方法:

1) add():这个方法用来给链表添加节点,扩展链表。

public void add(int value){
        ListNode newNode = new ListNode(value);

        if (head == null){
            head = newNode;
            current = newNode;
        }
        else {
            current.next = newNode;
            current = newNode;
        }
    }

在这个类内部,初始化一个新节点,并赋予传入的值(value):

ListNode newNode = new ListNode(value);

其实这一句就已经足够把新节点创造好了。

但是既然是链表,就必须把他们“链”起来,为下一个节点做好链接,因此才有了下面的判断语句:

if (head == null){
            head = newNode;
            current = newNode;
        }

判断一下头节点是不是空的(null),如果是的话,那就直接把当前节点作为头节点:

# 把传入的value作为值而创建的节点,赋予head,使之成为:头节点
            
head = newNode;


# cerrent作为指针,将该节点赋予current

current = newNode;

如果头节点不是空(null)的话,说明这个链表已经有头节点了

2) display()。用来将链表中的元素(对象)都打印显示出来。

public void display(ListNode list){
        ListNode temp = list;
        while (temp != null){
            int value = temp.val;
            System.out.println("value: "+ value);
            temp = temp.next;
        }
    }

传入一个“list” 头节点,在非空的情况下,把整个链表拉出来,每个元素挨个 println~

3) SeqHead()

public ListNode SeqHead(){
        return head;
    }

专门用来向外部返回这个链表的头节点,也就是链表本身。


3. ReverseSolution

public class ReverseSolution {
    public ListNode reverseList(ListNode head) {
        ListNode reverse = null;
        ListNode current = head;

        while (current != null){
            ListNode tempNext = current.next;
            current.next = reverse;
            reverse = current;
            current = tempNext;
        }
        return reverse;
    }
}

这个类的功能是获取头节点(传入的参数是一个头节点“head”,前文可知,头节点即是链表本体),然后进行翻转,这部分逻辑就非常简单了~~

成员变量

首先得抓住两个节点,一个是“reverse” ,一个是“current”

        ListNode reverse = null;
        ListNode current = head;

其中,“reverse” 是逻辑上安插在“head”头节点之前的一个节点。

这句话按照人话怎么理解呢?那当然是理解不了,咱们上图——

图中的 “pre” 节点就是我们这个代码中的 “reverse” 节点。

在逻辑中,我们假设它在头节点 “head” 之前,值为 null。没错,你想到什么了?

这位同学回答得好!

每一个链表最后一个节点也是 null!

因此,初始值为 null 的 ”reverse" 节点,相当于是把这个链表的最后一个节点预先放在了头节点 head 之前。

但是这个 null 光在放在图中的前头还不行,虽然我们脑袋中的逻辑是这样没错,但是得让计算机把方向指过去才OK。

怎么指过去呢?调用一下 next。

现在我们搞清楚了“reverse”的用途,再看下“current”。

ListNode current = head;

由于翻转链表的这个类传入了一个链表的头节点,因此我们直接将头节点赋值给 “current” 节点。

相当于一开局,就把当前指针指向了头节点。

当当前节点(指针)的指向非空(!= null)时

while (current != null)

创建一个临时节点,用来存储当前节点的对象地址,以便于在后面进行交换操作。这个操作非常类似冒泡排序时的数值交换,不过需要注意的是,冒泡操作时值传递,这里需要传递的是指针方向。

# 此时,我们的current 的值是 head,current.next的意思是,将head的下一个节点赋值给临时节点

ListNode tempNext = current.next;

这时,“tempNext” 节点帮我们保存了 “头节点 -> 第二节点” 的节点信息(地址),也可理解为保存了第二节点本身。

然后将初始值为 null 的 “reverse” 节点赋值给第二节点,第二节点的信息(地址)就被 null 给覆盖了:

current.next = reverse;

将 “current” 当前节点赋值给 reverse 节点,“reverse” 的值由“null -> head”。

此时,“reverse” 正式继承了 "head" 节点的值,取而代之成为了新的 “head”。

reverse = current;

到这里,我们稍微梳理一下——

  • 本来呢,这几个节点对应的数据的顺序是这样的:

        ListNode reverse = null;
        ListNode current = head;

        ListNode tempNext = current.next;

  • 然后,经过一番操作变成了这样:

红字部分的两步操作对应下面两行代码:

       1.   current.next = reverse;
       2.   reverse = current;

这时,聪明的小朋友们又有新发现了!

上图所示,啊不对,上上图所示,“head” 已经指向了 null,代表链表第一个节点已经翻转过来了。

此时,“reverse”、“head”、 “current” 三个节点参数都指向同一个数据(图中为字母“A”),这时我们的“current” 就再指着头节点的值就不合适了。

“current” 就得后移,那么谁还保存着指向第二节点的信息呢?

如图易得:是“tempNext”。

因此,最后一步,是把“tempNext” 的值赋予 “current”:

current = tempNext;

这个时候我们注意到,这“reverse” 和 “current” 节点所在的相对位置又回到了最初的起点:“reverse” 在左,“current”在右,但是 null 与头节点“head”之间的指向却已经被翻转了。

好,循环做这个操作,直到永远 —— 不是,直到把所有元素的指向都翻过来。


4. main 函数(主函数)

public class main {
    public static void main(String[] args){
        ReverseSolution reversed = new ReverseSolution();
        sequanceList list = new sequanceList();

        int[] intList = {3, 5, 7, 9};

        for (int element: intList){
            list.add(element);
        }
        System.out.println("Sequence of the Linked List below:");
        list.display(list.SeqHead());
        System.out.println("-------------------------------------------------------------");
        System.out.println("Reversed Sequence of the Linked List below:");
        list.display(reversed.reverseList(list.SeqHead()));

    }

前两行的:

        ReverseSolution reversed = new ReverseSolution();
        sequanceList list = new sequanceList();

我们将这两个类分别创建对象,以调用两个类中的函数进行操作。

后面就很简单了,创建一个数组:

int[] intList = {3, 5, 7, 9};

挨个添加到链表中去:

for (int element: intList){
            list.add(element);
        }

获取这个链表的头节点,看看是不是以上面那个数组为蓝本,成功地创建了这个链表:

        System.out.println("Sequence of the Linked List below:");
        list.display(list.SeqHead());

然后就是见证奇迹的时刻:

        System.out.println("Reversed Sequence of the Linked List below:");
        list.display(reversed.reverseList(list.SeqHead()));

我们马上回来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值