链表(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; // 程序正常结束
}