楼主: 风间fengjian
71 0

链表反转多种操作(传头指针) [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

小学生

14%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
40 点
帖子
3
精华
0
在线时间
0 小时
注册时间
2018-11-18
最后登录
2018-11-18

楼主
风间fengjian 发表于 2025-11-17 17:29:49 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

目录

方法一:经典迭代法(空间最优)

方法二:递归法

方法三:穿针引线法

方法四:头插法

方法五:数组辅助法

方法一:经典迭代法(空间最优)

迭代法是链表反转的最佳方案(空间复杂度 O(1)、时间复杂度 O(n)),主要思想是使用 3 个指针遍历链表,逐步反转每个节点的方向。

代码如下

ListNode* reverseList(ListNode* head) {
    ListNode* prev = NULL;
    ListNode* curr = head;
    ListNode* nextTemp = NULL;

    while (curr != NULL) {
        nextTemp = curr->next;
        curr->next = prev;
        prev = curr;
        curr = nextTemp;
    }
    return prev;
}

讲解

步骤1:指针初始化逻辑

ListNode* prev = NULL;
ListNode* curr = head;
ListNode* nextTemp = NULL;
prev

前驱节点,初始值为

NULL
。由于反转后,原链表的首节点将变为尾节点,而尾节点的
next
必须指向
NULL
,因此
prev
NULL
开始。

curr

当前需处理的节点,初始值为原链表头

head
,意味着从首个节点开始反转。

nextTemp

临时缓存指针,初始值为

NULL
。功能是保存当前节点的下一个节点地址——如果不保存,在执行
curr->next = prev
反转指针后,将会丢失后续节点的地址,导致链表断裂。

步骤2:循环遍历核心逻辑(while 循环内)

循环条件

curr != NULL
:只要当前节点存在(未处理完所有节点),则继续反转。

步骤3:保存后继节点

nextTemp = curr->next;

示例:原链表

1->2->3->4->5
,首次循环时
curr=1
curr->next=2
,因此
nextTemp=2

这一步是 “保命操作”:如果不保存,后续

curr->next
将被改写为
prev
NULL
)后,就无法找到节点
2
,链表会直接断裂。

步骤4:反转当前节点指针

curr->next = prev;

关键反转操作:使当前节点的

next
指针从指向下一个节点,更改为指向前一个节点。

示例:首次循环时

curr=1
prev=NULL
,因此
1->next = NULL
(此时节点 1 已经变成 “尾节点” 的雏形);第二次循环
curr=2
prev=1
,因此
2->next=1
(节点 2 指向节点 1,完成 1 和 2 的反转)。

步骤5:更新前驱节点

prev = curr;

prev
移动到当前节点
curr
,因为下一次循环要处理的节点(
nextTemp
),其前驱即是当前节点。

示例:首次循环后

prev=1
(下一次处理节点 2 时,2 的前驱是 1);第二次循环后
prev=2
(下一次处理节点 3 时,3 的前驱是 2)。

步骤 6:更新当前节点

curr = nextTemp;

curr
移动到先前保存的
nextTemp
(即原链表的下一个节点),继续处理下一个节点。

示例:首次循环后

curr=2
(开始处理节点 2);第二次循环后
curr=3
(开始处理节点 3),依此类推。

步骤7:循环结束与返回值

return prev;

循环结束条件:

curr == NULL
(所有节点已处理完毕)。

此时

prev
的指向:原链表的最后一个节点(例如:原链表尾是 5,循环结束时
prev=5
)。

因为所有节点已经反转,原尾节点变为了新链表的首节点,所以返回

prev
即是反转后的新表头。

方法二:递归法

递归法反转链表的核心是从底部向上处理,首先递归至链表尾部找到新的表头,然后从尾部逆向调整每个节点的指针方向。

代码如下

ListNode* reverseList(ListNode* head) {
    if (head == NULL || head->next == NULL) {
        return head;
    }

    ListNode* newHead = reverseList(head->next);

    head->next->next = head; 
    head->next = NULL; 

    return newHead;
}

讲解

步骤1:递归终止条件

if (head == NULL || head->next == NULL) {
    return head;
}

两种终止情况:

  • 空链表(
    head == NULL
    ):直接返回
    NULL
    ,没有节点需要反转;
  • 只有一个节点(
    head->next == NULL
    ):无需反转,返回该节点本身(此节点将成为最终反转链表的首节点,即原链表的尾节点)。

作用:递归到达链表最末端时停止,开始 “回溯” 并处理指针反转。

步骤2:递归调用(从上至下,直至链表末尾)

ListNode* newHead = reverseList(head->next);

逻辑:不是先处理当前节点,而是优先递归处理
当前节点之后的子链表

例:原链表
1->2->3->4->5

,调用顺序为:
reverseList(1) → reverseList(2) → reverseList(3) → reverseList(4) → reverseList(5)

当递归到达
reverseList(5)

时,触发停止条件(
5->next == NULL

),返回
5

,此时
newHead = 5

(这将是最终反转链表的新头部,会持续向上返回)。

步骤3:回溯阶段:调整指针方向
递归调用返回后,进入“回溯”阶段,从链表末尾开始逆向调整指针:
步骤4:使后继节点指向当前节点

head->next->next = head;

例:回溯到
reverseList(4)

时,
head=4


head->next=5

(此时
5

已被返回作为
newHead

),执行
5->next = 4

,完成
4


5

的反转(原本
4->5

变为
5->4

)。
再回溯到
reverseList(3)

时,
head=3


head->next=4

,执行
4->next=3

(原本
3->4

变为
4->3

),依此类推。

步骤5:断开当前节点与原后继的连接

head->next = NULL;

作用:防止形成环状链表。如果不断开,例如在
head=4

时,原本的
4->5

已更改为
5->4

,如果
4->next

仍然指向
5

,将会形成
4?5

的环。
例:
head=4

时,执行
4->next = NULL

,此时
5->4->NULL

,随后回溯到
3

时,再执行
4->next=3

即可。

步骤6:向上传递新头部

return newHead;

无论回溯到哪一层,
newHead

始终是原链表的末节点(即反转后的新的头部),通过返回值一路向上传递,最终作为整个函数的结果返回。

方法三:穿针引线法
穿针引线的核心思想是从第二个节点起,依次将节点“头插”到原链表头部,逐步重建反转链表。
代码如下

struct ListNode* reverseList(struct ListNode* head) {
    if(head == NULL || head -> next ==NULL){
        return head;
    }
    struct ListNode* curr = head;
    while(curr -> next != NULL){
        struct ListNode* temp = curr -> next;
        curr -> next = temp -> next;
        temp -> next = head;
        head = temp;
    }
    return head;
}

讲解
步骤1:边界处理
if(head == NULL || head -> next == NULL){
    return head;
}

空链表(
head == NULL

):没有节点可以反转,直接返回
NULL


单个节点(
head->next == NULL

):无需反转,返回其本身;

步骤2:初始化当前节点指针

struct ListNode* curr = head;

curr

始终指向“当前未处理部分的链表头部”,初始为原链表头部
head


作用:作为固定点,用于定位待头插的节点(
curr->next

),并保持未处理部分的连接。

步骤3:核心循环:头插操作四步曲
循环条件

curr->next != NULL

:只要
curr

后面仍有节点,就继续将其头插至链表头部。

步骤4:保存待头插节点

struct ListNode* temp = curr -> next;

例:原链表
1->2->3->4->5

,首次循环时
curr=1


curr->next=2

,因此
temp=2


作用:暂时保存待头插的节点,避免后续操作断开连接后丢失该节点。

步骤5:断开待头插节点与原链表的连接

curr -> next = temp -> next;

例:首次循环中,
temp=2


temp->next=3

,执行后
curr->next=3

(即
1->3

);
作用:将
curr

与待头插节点
temp

断开,直接连接到
temp

的下一个节点,确保未处理部分的链表不断开。

步骤6:将 temp 节点头插到链表头部

temp -> next = head;

例:首次循环中,原
head=1

,执行后
temp->next=1

(即
2->1

);
作用:使待头插的
temp

节点指向当前链表的头部,完成“头部插入”的前置连接。

步骤7:更新表头为 temp

head = temp;

例:首次循环后,
head=2

,此时链表变为
2->1->3->4->5


作用:确认
temp

成为新的链表头部,完成一次头插操作。

步骤8:循环结束与返回值

return head;

循环终止条件:
curr->next == NULL

(所有节点都已头插至头部,未处理部分为空);
此时
head

指向原链表的最后一个节点(如例子中的
5

),即反转后链表的新头部,直接返回即可。

方法四:头插法
核心是用

dummy

作为虚拟头部(初始为
NULL

),遍历原链表时将每个节点依次“头插”到
dummy

前方,最终
dummy

成为反转后链表的头部
代码如下
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* dummy = NULL;
    struct ListNode* cur = head;
    while(cur != NULL){
        struct ListNode* temp = cur;
        cur = cur -> next;
        temp -> next = dummy;
        dummy = temp;
    }
    return dummy;
}

步骤1:指针初始化逻辑

struct ListNode* dummy = NULL;
struct ListNode* cur = head;

dummy

:实质是“反转链表的虚拟头部”,初始为
NULL

(表示反转链表最初是空的)。主要作用是作为固定点,接收原链表节点的头插,最终成为反转链表的实际头部。
cur

:遍历原链表的指针,从原表头
head

开始,负责逐一取出节点进行反转操作。
循环条件
cur != NULL

:只要原链表还有未处理的节点,就继续头插。

步骤2:保存当前待头插节点

struct ListNode* temp = cur;

例:原链表
1->2->3->4->5

,首次循环时
cur=1

temp=1
(储存待插入的节点 1)。 功能:之后
cur
将移至下一节点,必须先储存当前节点,以防丢失。 步骤3:cur 预先移至下一节点
cur = cur -> next;
示例:首次循环中,
cur
由 1 转至 2,预先 “获取下一节点”,防止后续
temp->next = dummy
更改指针后,无法找到原链表的后续节点。 要点:此步为 “断开连接前的预先探索”,保证遍历过程不间断。 步骤4:将 temp 头部插入反转链表
temp -> next = dummy;
示例:首次循环时
dummy=NULL
,执行后
temp->next=NULL
(节点 1 的 next 指向空位,成为反转链表的首节点);二次循环时
dummy=1
,执行后
temp->next=1
(节点 2 指向 1,成为新的反转链表头部)。 核心:使当前节点
temp
的 next 指针,指向已构建的反转链表(
dummy
指向的部分),完成 “头部插入” 的主要连接。 步骤5:更新反转链表的头部节点
dummy = temp;
示例:首次循环后
dummy=1
(反转链表为
1->NULL
);二次循环后
dummy=2
(反转链表为
2->1->NULL
),依此类推。 功能:确认
temp
成为新的反转链表头部节点,
dummy
始终指向最新的反转链表头部。 步骤6:循环终止与返回值
return dummy;
cur=NULL
时,原链表的所有节点已全部处理完毕,
dummy
指向最后插入的节点(原链表的尾节点),即反转后链表的头部节点,直接返回即可。 方法五:数组辅助法 先将原链表所有节点的值存入数组,再反向遍历数组,把值重新赋给原链表节点,间接达到 “链表反转” 的效果。此方法逻辑清晰,无需复杂的指针操作,适合初学者理解。 代码如下
struct ListNode* reverseList(struct ListNode* head) {
    if(head == NULL){
        return head;
    }
    int count = 0;
    struct ListNode* curr = head;
    while(curr != NULL){
        count++;
        curr = curr -> next;
    }

    int* arr = (int*)malloc(sizeof(int) * count);

    curr = head;
    int i = 0;
    while(curr != NULL){
        arr[i++] = curr -> val;
        curr = curr -> next;
    }

    curr = head;
    for(int k = i - 1; k >= 0; k--){
        curr -> val = arr[k];
        curr = curr -> next;
    }
    return head;
}
解析 步骤1:边缘情况处理
if(head == NULL){
    return head;
}
场景:如果输入链表为空(
head=NULL
),没有节点可以处理,直接返回
NULL
,避免后续循环出错。 功能:涵盖极端情况,增强代码的稳健性。 步骤2:统计链表节点总数
int count = 0;
struct ListNode* curr = head;
while(curr != NULL){
    count++;
    curr = curr -> next;
}
逻辑:使用
curr
指针遍历原链表,每遇到一个节点,
count
(节点总数)加 1,直至
curr=NULL
(遍历结束)。 示例:原链表
1->2->3->4->5
,遍历后
count=5
。 功能:确定存储节点值的数组长度(数组需能容纳所有节点的值)。 步骤3:创建数组存储节点值
int* arr = (int*)malloc(sizeof(int) * count);
逻辑:动态分配一个 int 类型数组,长度为
count
(节点总数),用于存储所有节点的初始值。 注意:原代码未释放该数组,会造成内存泄漏,需在函数结尾处添加
free(arr)
(已在注释中补充)。 步骤4:节点值存入数组
curr = head;
int i = 0;
while(curr != NULL){
    arr[i++] = curr -> val;
    curr = curr -> next;
}
逻辑: 重设
curr
为原表头(之前遍历后
curr=NULL
,需重新指向表头); 使用
i
作为数组索引,遍历时将每个节点的
val
存入数组,
i
自动递增。 示例:原链表
1->2->3->4->5
,数组存储后
arr = [1,2,3,4,5]
i=5
(索引最终指向数组末尾后一位)。 步骤5:反向赋值,实现 “反转”
curr = head;
for(int k = i - 1; k >= 0; k--){
    curr -> val = arr[k];
    curr = curr -> next;
}
逻辑: 再次重设
curr
为原表头; 数组索引
k
i-1
(数组最后一个元素的索引)开始,反向遍历数组; 将数组反向的值赋给原链表节点,
curr
同步移动,直至所有节点赋值完成。 示例:数组
[1,2,3,4,5]
反向后为
5,4,3,2,1
,赋值后原链表节点值变为
5->4->3->2->1
,实现 “反转” 效果。 步骤6:返回结果
free(arr);  // 补充:释放数组内存,避免内存泄漏
return head;
要点:返回的是 原链表的表头指针 (链表的指针结构完全不变,仅节点存储的值被反向替换)。 示例:原表头是
1
的节点,赋值后该节点值变为
5
,依然作为新链表的表头返回。 本文结束,感谢阅读
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:Reverse struct RETURN Dummy Count

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
扫码
拉您进交流群
GMT+8, 2026-2-10 19:55