C/C++ 每日一练:单链表的反转

链表(Linked List)

         链表是一种线性数据结构,由一系列节点(Node)通过指针链接在一起。与数组不同,链表中的元素在内存中不需要连续存储,每个节点包含两部分:

  • 数据部分:存储节点的值或数据。
  • 指针部分:存储指向下一个节点的地址(单链表)或上一个和下一个节点的地址(双向链表)。

         链表的类型主要有以下几种:

  • 单链表:每个节点只指向下一个节点。
  • 双向链表:每个节点既有指向下一个节点的指针,也有指向上一个节点的指针。
  • 循环链表:链表的最后一个节点指向链表的头节点,形成循环。

         链表的特点:

  • 动态大小:可以根据需要动态地增加或减少元素,无需预分配存储空间。
  • 插入/删除效率高:在链表的任意位置进行插入或删除操作只需修改指针,不涉及大量元素的移动,效率较高。
  • 随机访问效率低:由于链表不支持直接访问任意位置的元素,需要通过遍历来查找特定位置的节点。

         如下图所示:

题目要求

        实现一个单链表的反转函数。给定一个单链表的头节点,将该链表反转,并返回新的头节点(即反转前的最后一个节点)。不允许改变链表节点的位置,要求就地反转链表,即在原链表上直接修改节点指针的方向。

做题思路

1、定义链表节点

        首先,需要定义一个结构体来表示链表节点,其中包含存储数据的字段和指向下一个节点的指针。

2、反转过程

        初始化三个指针:

  • prev(前一个节点),初始为nullptr;
  • curr(当前节点),初始为头节点;
  • next(下一个节点),用于临时存储当前节点的下一个节点,防止链表断裂。

        遍历链表,在每次迭代中:

  • 将next指针指向curr的下一个节点。
  • 将curr的next指针指向prev,实现反转。
  • 将prev和curr分别移动到curr和next的位置,继续遍历。
  • 当遍历完成时,prev将指向新的头节点(curr指针此时已在最后一次while循环中被指向原来尾结点的空指针)。

3、返回结果:返回新的头节点指针prev。

过程解析

  • 在反转过程中,需要小心处理指针的指向,确保链表不会断裂。
  • 使用临时指针next来保存当前节点的下一个节点是非常重要的,因为在反转curr节点的next指针之前,必须知道下一个节点是什么,以便在迭代中继续前进。
  • 反转完成后,原来的头节点将变为尾节点,其next指针在第一次while循环中被指向nullptr。

运用到的知识点

  • 结构体定义和初始化。
  • 指针操作,包括指针的声明、赋值和比较。
  • 循环结构,用于遍历链表。
  • 基本的内存管理概念(虽然在这个示例中没有显式释放内存,但在实际应用中需要注意避免内存泄漏)。

复杂度分析

  • 时间复杂度:每个节点遍历一次,时间复杂度为O(n),其中n为链表节点个数。
  • 空间复杂度:由于只使用了固定的额外指针空间,没有使用额外的存储结构,因此空间复杂度为O(1)。

示例代码

C 实现:

#include <stdio.h> // 包含标准输入输出头文件,用于输入输出操作  
#include <stdlib.h> // 包含标准库头文件,用于动态内存分配等操作  

// 定义链表节点结构  
struct Node {
    int data; // 节点存储的数据  
    struct Node* next; // 指向下一个节点的指针  
};

// 反转单链表函数  
struct Node* reverseLinkedList(struct Node* head) {
    struct Node* prev = NULL;  // 用于保存前一个节点,初始化为NULL,因为头节点前面没有节点  
    struct Node* curr = head;  // 当前节点,初始化为链表头  
    struct Node* next = NULL;  // 用于保存下一个节点,防止链表在反转过程中断裂  

    // 遍历链表并反转指针  
    while (curr != NULL) { // 当当前节点不为NULL时,继续循环  
        next = curr->next;    // 保存当前节点的下一个节点,因为接下来当前节点的next会被改变  
        curr->next = prev;    // 将当前节点的next指向前一个节点,实现指针反转  
        prev = curr;          // 前一个节点移动到当前节点位置,准备下一个节点的反转  
        curr = next;          // 当前节点移动到下一个节点,继续反转操作  
    }

    return prev;  // 当链表遍历完成,prev将指向新的头节点,返回它  
}

// 打印链表函数  
void printList(struct Node* head) {
    struct Node* temp = head; // 使用临时指针遍历链表  
    while (temp != NULL) { // 当临时指针不为NULL时,继续循环  
        printf("%d -> ", temp->data); // 打印当前节点的数据  
        temp = temp->next; // 移动到下一个节点  
    }
    printf("NULL\n"); // 打印链表结束标志  
}

// 创建新节点函数  
struct Node* createNode(int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); // 动态分配内存给新节点  
    newNode->data = data; // 设置新节点的数据  
    newNode->next = NULL; // 新节点的next指针初始化为NULL,表示它后面没有节点  
    return newNode; // 返回新节点的指针  
}

int main() 
{
    // 创建链表 1 -> 2 -> 3 -> 4 -> 5 -> NULL  
    struct Node* head = createNode(1); // 创建头节点,数据为1  
    head->next = createNode(2); // 创建第二个节点,数据为2,链接到头节点后面  
    head->next->next = createNode(3); // 创建第三个节点,数据为3,链接到第二个节点后面  
    head->next->next->next = createNode(4); // 创建第四个节点,数据为4,链接到第三个节点后面  
    head->next->next->next->next = createNode(5); // 创建第五个节点,数据为5,链接到第四个节点后面  

    printf("原始链表: \n"); // 打印提示信息  
    printList(head); // 打印原始链表  

    // 反转链表  
    head = reverseLinkedList(head); // 调用反转函数,并将返回的新头节点赋值给head  

    printf("反转后的链表: \n"); // 打印提示信息  
    printList(head); // 打印反转后的链表  

    return 0; // 程序正常结束  
}

C++ 实现:

#include <iostream> // 引入标准输入输出流库  
using namespace std; // 使用标准命名空间,简化代码中的std::前缀  

// 定义一个链表节点的结构体  
struct Node {
    int data; // 节点中存储的数据  
    Node* next; // 指向下一个节点的指针  
    // 构造函数,用于初始化节点数据并设置next指针为nullptr  
    Node(int val) : data(val), next(nullptr) {}
};

// 反转单链表的函数  
Node* reverseLinkedList(Node* head) {
    Node* prev = nullptr; // 定义一个指针prev用于保存前一个节点,初始化为nullptr  
    Node* curr = head; // 定义一个指针curr指向当前节点,初始化为链表头节点  
    Node* next = nullptr; // 定义一个指针next用于临时保存当前节点的下一个节点,防止链表断裂  

    // 遍历链表并反转指针方向  
    while (curr != nullptr) {
        next = curr->next; // 保存当前节点的下一个节点  
        curr->next = prev; // 将当前节点的next指针指向前一个节点,实现反转  
        prev = curr; // 将prev指针移动到当前节点位置  
        curr = next; // 将curr指针移动到下一个节点,继续反转  
    }

    return prev; // 当遍历完成后,prev将指向新的头节点,返回它  
}

// 打印链表的函数  
void printList(Node* head) {
    Node* temp = head; // 定义一个临时指针temp用于遍历链表  
    while (temp != nullptr) {
        cout << temp->data << " -> "; // 打印当前节点的数据  
        temp = temp->next; // 移动到下一个节点  
    }
    cout << "NULL" << endl; // 打印链表结束标志  
}

// 创建一个新节点的函数  
Node* createNode(int data) {
    return new Node(data); // 使用构造函数创建一个新节点,并返回其指针  
}

int main() 
{
    // 创建链表 1 -> 2 -> 3 -> 4 -> 5 -> NULL  
    Node* head = createNode(1); // 创建头节点,数据为1  
    head->next = createNode(2); // 创建第二个节点,数据为2,并链接到头节点后  
    head->next->next = createNode(3); // 创建第三个节点,数据为3,并链接到第二个节点后  
    head->next->next->next = createNode(4); // 创建第四个节点,数据为4,并链接到第三个节点后  
    head->next->next->next->next = createNode(5); // 创建第五个节点,数据为5,并链接到第四个节点后  

    cout << "原始链表: " << endl; // 打印提示信息  
    printList(head); // 调用printList函数打印原始链表  

    // 反转链表  
    head = reverseLinkedList(head); // 调用reverseLinkedList函数反转链表,并更新头节点指针  

    cout << "反转后的链表: " << endl; // 打印提示信息  
    printList(head); // 调用printList函数打印反转后的链表  

    return 0; // 程序正常结束  
}

猜你喜欢

转载自blog.csdn.net/weixin_60461563/article/details/143117511