牛客网反转链表c++版,详细介绍语法(包括完整代码)

链接:题目地址

双指针法

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* temp; // 保存cur的下一个节点
        ListNode* cur = head;
        ListNode* pre = NULL;
        while(cur) {
            temp = cur->next;  // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur->next = pre; // 翻转操作
            // 更新pre 和 cur指针
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

一些语法说明:

  1. ListNode* 是指向链表节点的指针类型,它是这段代码中的一个固定的语法。

  2. reverseList 是一个函数的名称,它用于反转一个单链表。在这段代码中,它是开发人员自己起的一个名称,不是固定的语法。你可以选择其他名称,只要名称能够清晰地表达函数的用途即可。

  3. ListNode* head 是定义一个指向单链表头节点的指针。在这段代码中,head 是函数 reverseList 的参数之一,它的作用是指向单链表的头节点。使用指针类型的好处是可以通过改变指针指向的地址来改变链表的结构,而不必每次都对整个链表进行复制。同时,在函数调用中使用指针作为参数可以避免在函数内部对参数进行复制,提高程序的效率。

  4. ListNode* head 是函数 reverseList 的形参,变量名 head 可以随意更改,但是为了代码的可读性和易于理解,最好选择一个有意义的变量名来表示单链表的头节点,例如 listHead 或 pHead 等等。无论使用什么变量名,变量的类型应该保持不变,即 ListNode*,这是指向单链表节点的指针类型。

  5. ListNode* temp; // 保存cur的下一个节点 ListNode* cur = head; ListNode* pre = NULL;
    ListNode* temp; 定义了一个指向 ListNode 类型的指针变量 temp,它的初始值未定义。

    ListNode* cur = head; 定义了一个指向 ListNode 类型的指针变量 cur,它被初始化为指向单链表的头节点 head。

    ListNode* pre = NULL; 定义了一个指向 ListNode 类型的指针变量 pre,它被初始化为空指针。

    指针类型用于存储变量的地址,因此在这里等号后面的部分表示变量的初始值或初始化表达式,它们可以是一个地址、一个变量或一个表达式。在这里,等号右侧的 head 和 NULL 都是指针类型的常量,它们分别表示单链表的头节点和空指针。

    需要注意的是,指针变量在定义时,其初始值是未定义的,这意味着指针变量可以包含任意值,包括一个无效的地址。因此,在使用指针变量之前,应该将其初始化为一个合法的地址或空指针,以避免不可预测的行为。使用指针的原因是因为单链表是由节点组成的,每个节点包含了数据域和指向下一个节点的指针域,所以在对单链表进行操作时,需要通过指针来访问和修改链表中的节点。

  6. while(cur) 中的括号表示条件表达式的开始和结束,它包含了一个判断条件 cur。在 C++ 中,任何非零的指针值都被视为 true,而空指针值 nullptr 或 NULL 被视为 false。因此,当指针 cur 不为空时,条件表达式的值为 true,循环会一直执行;当指针 cur 为空时,条件表达式的值为 false,循环停止。

    因此,while(cur) 可以简化为 while(cur != nullptr) 或 while(cur != NULL) 或 while(cur),它们的含义是相同的。在实际编码中,一般使用简洁的写法 while(cur),因为它既简单又易于理解。

递归法

class Solution {
public:
    ListNode* reverse(ListNode* pre,ListNode* cur){
        if(cur == NULL) return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
        // 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
        // pre = cur;
        // cur = temp;
        return reverse(cur,temp);
    }
    ListNode* reverseList(ListNode* head) {
        // 和双指针法初始化是一样的逻辑
        // ListNode* cur = head;
        // ListNode* pre = NULL;
        return reverse(NULL, head);
    }

};

一些语法说明:

  1. return reverse(NULL, head) 是调用 reverse 函数的语法,返回从头节点开始反转后的链表。在这个递归函数中, reverse(pre,cur) 的作用是返回从节点 cur 开始反转的链表,其中 pre 是 cur 的前一个节点。当递归到链表的末尾时,cur 将为 NULL,此时 pre 就成为了反转后的链表的头结点,所以递归函数返回 pre。而在调用 reverseList 函数时,初始时 pre 被设置为 NULL,即 pre 表示反转的头节点的前一个节点为 NULL,这样 reverse(NULL, head) 就能够返回从 head 开始反转的链表。

  2. return reverse(cur,temp); 是一个递归调用语句,它的作用是反转从当前节点 cur 的后续节点 temp 开始的链表,并将反转后的链表的头节点返回。这里的 reverse 函数是类中的一个成员函数,它是通过递归调用自身来实现链表反转的。

    具体来说,reverse(cur,temp) 函数的作用是反转从当前节点 cur 的后续节点 temp 开始的链表,并将反转后的链表的头节点返回。在实现过程中,首先通过 temp 暂存 cur 的下一个节点,然后将 cur 的 next 指针指向 pre,其中 pre 是 cur 的前一个节点,表示在反转链表过程中 cur 的前一个节点就是反转后链表的下一个节点。接下来,将 pre 设置为 cur,cur 设置为 temp,以便继续反转链表的下一个节点。最后,通过递归调用 reverse(cur,temp),反转链表的剩余部分,直到链表的末尾为止。当 cur 为 NULL 时,返回 pre,表示反转后的链表的头节点。

  3. 递归调用语句是一种在函数中调用函数本身的语法。在 C++ 中,递归调用语句的语法与普通函数调用语句的语法相同,只不过在调用过程中会反复调用同一个函数,直到达到递归终止条件才会停止。一个典型的递归函数包含两个部分:基本情况和递归情况。在基本情况中,递归终止,返回最终的结果。而在递归情况中,函数会调用自身,将问题规模不断缩小,直到达到递归终止条件为止。

    • 基本情况指的是递归结束的条件,也称为递归终止条件。在基本情况下,函数直接返回结果,不再进行递归调用。递归终止条件是必须要设置的,否则递归会一直进行下去,导致栈溢出等错误。

    • 递归情况指的是函数调用自身的部分,也称为递归调用。在递归调用时,问题的规模通常会缩小,以便逐步接近递归终止条件。递归函数在递归调用之前需要先判断是否已经到达递归终止条件,如果已经到达,则直接返回结果,否则进行递归调用。

    • 在上面的代码中,递归函数 reverse 包含两个部分:基本情况和递归情况。

      基本情况是在 reverse 函数中,当参数 cur 为 NULL 时,返回参数 pre,表示反转后的链表的头节点。这种情况表示递归到了链表的末尾,无法继续反转链表。

      递归情况是在 reverse 函数中,当参数 cur 不为 NULL 时,将当前节点 cur 的后续节点 temp 反转,并将反转后的链表的头节点返回。这种情况表示还可以继续反转链表,因此通过递归调用 reverse(cur,temp) 来继续反转链表的剩余部分。

      因此,这个递归函数的基本情况是当链表末尾被处理时,返回反转后的链表的头节点;而递归情况则是将当前节点的后续节点反转,并通过递归调用继续反转剩余的部分。

  4. 在递归调用时,需要注意设置递归终止条件,以避免出现无限递归的情况。在上面的代码中,递归终止条件是cur == NULL,表示链表的末尾已经处理完毕,无法再反转链表的剩余部分。当 cur == NULL 时,函数将返回参数 pre,表示反转后的链表的头节点。这种情况下,递归调用将停止,反转链表的过程也就结束了。因此,这个递归函数的终止条件是当 cur 为 NULL 时,表示已经到达了链表的末尾。

递归函数的定义

当函数在同一个函数内调用时,它被称为C++中的递归。 调用相同函数的函数(函数自已调用自已)称为递归函数。
在函数调用之后调用自身并且不执行任何任务的函数称为尾递归。 在尾递归中,我们通常使用return语句调用相同的函数。

递归函数的基本形式:

返回值类型 函数名A(参数列表) {
    if (终止条件) {
        return  返回值;
    }
    else
     return 函数名A(参数列表);//调用函数的A自己
}

完整代码双指针法

#include <iostream>
using namespace std;

struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* temp; // 保存cur的下一个节点
        ListNode* cur = head;
        ListNode* pre = NULL;
        while (cur) {
            temp = cur->next;  // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur->next = pre; // 翻转操作
            // 更新pre 和 cur指针
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

int main() {
    ListNode* head = new ListNode(1);
    head->next = new ListNode(2);
    head->next->next = new ListNode(3);
    head->next->next->next = new ListNode(4);
    head->next->next->next->next = new ListNode(5);

    Solution sol;
    ListNode* newHead = sol.reverseList(head);

    while (newHead != NULL) {
        cout << newHead->val << " ";
        newHead = newHead->next;
    }
    cout << endl;
    // 在控制台窗口中停留一段时间,等待用户查看输出结果
    system("pause");
    return 0;
}

结构体:结构体是一种自定义的数据类型,可以用来组合不同类型的变量,并为这些变量指定一个名称。在 C++ 中,结构体可以包含成员变量、成员函数和构造函数,可以用来表示复杂的数据结构,比如链表、树、图等。

定义结构体的主要意义是将相关的数据和操作组织在一起,使程序更加模块化和易于维护。通过将数据和操作封装在一个结构体中,可以避免全局变量的使用,从而降低变量命名冲突的风险。此外,结构体还可以实现抽象数据类型,通过隐藏内部实现细节,使用户只能使用预定义的接口,从而提高代码的安全性和可重用性。

在实际编程中,结构体可以用来定义复杂的数据结构,比如链表、树、图等,也可以用来定义自己的数据类型,比如日期、时间、向量等。结构体还可以用来表示对象和实体,比如人、汽车、商品等,以便进行统一管理和操作。

  1. 这个ListNode结构体定义了一个链表的节点,包含两个成员变量:val 表示节点的值,next 是一个指向下一个节点的指针。它的作用是用来组成链表。

  2. 这个结构体还定义了一个构造函数 ListNode(int x),用来初始化节点的值和下一个节点的指针。在创建新节点的时候,可以通过传入参数来初始化节点的值。而下一个节点的指针一开始是 NULL,表示该节点为链表的末尾节点。具体如下

    ListNode(int x) 定义了一个参数为 int x 的构造函数,该函数用于创建一个带有整型数值的链表节点。
    冒号 : 后面的部分是构造函数的初始化列表,用于初始化 val 和 next 成员变量。
    val(x): 表示将 x 赋值给 val 成员变量。
    next(NULL) :表示将 NULL 赋值给 next 成员变量。
    因此,该构造函数的作用是初始化 ListNode 结构体的成员变量 val 和 next,其中 val 被初始化为传入的整型参数,next 被初始化为 NULL,即链表节点的下一个节点为空。

    可以在创建链表节点时直接调用该构造函数来完成节点的初始化操作,例如:

    ListNode* node = new ListNode(3);
    

    这段代码会创建一个整型数值为 3,下一个节点为空的链表节点。

  3. 构造函数语法:类名(){}

完整代码递归版

#include <iostream>

using namespace std;

struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
    ListNode* reverse(ListNode* pre, ListNode* cur) {
        if (cur == NULL) return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
        return reverse(cur, temp);
    }

    ListNode* reverseList(ListNode* head) {
        return reverse(NULL, head);
    }
};

int main() {
    // 初始化一个链表
    ListNode* head = new ListNode(1);
    head->next = new ListNode(2);
    head->next->next = new ListNode(3);
    head->next->next->next = new ListNode(4);
    head->next->next->next->next = new ListNode(5);

    // 反转链表
    Solution s;
    ListNode* new_head = s.reverseList(head);

    // 输出反转后的链表
    while (new_head != NULL) {
        cout << new_head->val << " ";
        new_head = new_head->next;
    }
    cout << endl;
    // 在控制台窗口中停留一段时间,等待用户查看输出结果
    system("pause");
    return 0;
}

参考文献

参考链接: 代码随想录

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值