链表(Linked List)
链表是一种线性数据结构,由一系列节点(Node)通过指针链接在一起。与数组不同,链表中的元素在内存中不需要连续存储,每个节点包含两部分:
- 数据部分:存储节点的值或数据。
- 指针部分:存储指向下一个节点的地址(单链表)或上一个和下一个节点的地址(双向链表)。
链表的类型主要有以下几种:
- 单链表:每个节点只指向下一个节点。
- 双向链表:每个节点既有指向下一个节点的指针,也有指向上一个节点的指针。
- 循环链表:链表的最后一个节点指向链表的头节点,形成循环。
链表的特点:
- 动态大小:可以根据需要动态地增加或减少元素,无需预分配存储空间。
- 插入/删除效率高:在链表的任意位置进行插入或删除操作只需修改指针,不涉及大量元素的移动,效率较高。
- 随机访问效率低:由于链表不支持直接访问任意位置的元素,需要通过遍历来查找特定位置的节点。
如下图所示:
题目要求
给定一个单链表,编写一个算法来查找链表的中间节点。如果链表长度为奇数,则返回唯一的中间节点;如果为偶数,则返回两个中间节点中的第一个。
示例
假设链表为:1 -> 2 -> 3 -> 4 -> 5
- 输出:节点 3
假设链表为:1 -> 2 -> 3 -> 4
- 输出:节点 2
做题思路
寻找链表的中间节点可以用快慢指针的方法。这种方法效率高且不需要额外的空间,适合单链表查找问题。
- 定义两个指针:slow 和 fast。
- slow 每次向前移动一步,fast 每次向前移动两步。
- 当 fast 指针遍历到达链表末尾时,slow 指针正好在链表的中间位置。
- 这种方法的时间复杂度为 O(n),空间复杂度为 O(1),是一种高效的解法。
过程解析
- 初始化两个指针 slow 和 fast,都指向链表的头节点。
- 通过循环控制 fast 每次移动两步,slow 每次移动一步。
- 当 fast 为 NULL 或 fast->next 为 NULL 时,结束循环,此时 slow 即为中间节点。
运用到的知识点
- 链表的基本操作和定义
- 指针的概念和指针操作
- 快慢指针算法技巧
- 时间和空间复杂度分析
示例代码
C 实现
cpp
#include <stdio.h> // 包含标准输入输出库,用于输出打印函数
#include <stdlib.h> // 包含标准库,用于内存分配
// 定义链表节点结构体
struct Node {
int data; // 节点数据部分,存储整型数据
struct Node* next; // 指向下一个节点的指针
};
// 创建新节点函数
struct Node* createNode(int data) {
// 为新节点分配内存空间
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
// 初始化新节点的数据
newNode->data = data;
// 将新节点的指针设置为 NULL,表示链表的末尾
newNode->next = NULL;
return newNode; // 返回创建好的新节点
}
// 查找链表中间节点的函数
struct Node* findMiddle(struct Node* head) {
struct Node *slow = head, *fast = head; // 定义并初始化快慢指针,均指向链表头节点
// 循环遍历链表,直到 fast 到达链表末尾
while (fast != NULL && fast->next != NULL) {
slow = slow->next; // 慢指针每次向前移动一步
fast = fast->next->next; // 快指针每次向前移动两步
}
return slow; // 当循环结束时,慢指针指向链表的中间节点
}
// 打印链表的函数
void printList(struct Node* head) {
struct Node* temp = head; // 定义一个临时指针,用于遍历链表
// 循环遍历链表,直到节点为空
while (temp != NULL) {
printf("%d -> ", temp->data); // 打印当前节点的数据
temp = temp->next; // 移动到下一个节点
}
printf("NULL\n"); // 链表末尾指示为 NULL
}
// 主函数
int main()
{
// 创建链表节点并连接,构造链表 1 -> 2 -> 3 -> 4 -> 5
struct Node* head = createNode(1);
head->next = createNode(2);
head->next->next = createNode(3);
head->next->next->next = createNode(4);
head->next->next->next->next = createNode(5);
printf("链表: ");
printList(head); // 调用函数打印链表
// 查找链表的中间节点
struct Node* middle = findMiddle(head);
if (middle != NULL) {
// 如果链表非空,打印中间节点的数据
printf("The data of the middle node is: %d\n", middle->data);
} else {
// 如果链表为空,提示信息
printf("the linked list is empty.\n");
}
return 0; // 程序结束
}
C++ 实现
cpp
#include <iostream> // 包含输入输出流库,用于控制台输出
using namespace std; // 使用标准命名空间,避免每次使用 std:: 前缀
// 定义链表节点类
class Node {
public:
int data; // 节点数据部分,存储整型数据
Node* next; // 指向下一个节点的指针
// 构造函数,用于创建新节点并初始化数据
Node(int data) {
this->data = data; // 初始化节点数据
this->next = nullptr; // 初始化指针为 nullptr 表示链表末尾
}
};
// 查找链表中间节点的函数
Node* findMiddle(Node* head) {
Node *slow = head, *fast = head; // 定义快慢指针,均初始化为链表头节点
// 循环遍历链表,直到 fast 或 fast->next 到达链表末尾
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next; // 慢指针每次移动一步
fast = fast->next->next; // 快指针每次移动两步
}
return slow; // 循环结束时,慢指针指向链表的中间节点
}
// 打印链表的函数
void printList(Node* head) {
Node* temp = head; // 定义临时指针,用于遍历链表
// 循环遍历链表,直到节点为空
while (temp != nullptr) {
cout << temp->data << " -> "; // 打印当前节点的数据
temp = temp->next; // 移动到下一个节点
}
cout << "NULL" << endl; // 链表末尾输出 NULL 表示链表终点
}
int main()
{
// 创建链表节点并连接,构造链表 1 -> 2 -> 3 -> 4 -> 5
Node* head = new Node(1);
head->next = new Node(2);
head->next->next = new Node(3);
head->next->next->next = new Node(4);
head->next->next->next->next = new Node(5);
cout << "链表: ";
printList(head); // 打印链表
// 查找链表的中间节点
Node* middle = findMiddle(head);
if (middle != nullptr) {
// 如果链表非空,打印中间节点的数据
cout << "The data of the intermediate node is: " << middle->data << endl;
} else {
// 如果链表为空,提示信息
cout << "The linked list is empty" << endl;
}
return 0; // 程序结束
}