最近更新于 2024-05-05 14:19
情景
初始链表

反转后

测试环境
gcc 9.4.0 – 64位
编译参数:
-no-pie -std=c17 -Wall -Werror=return-type -Werror=address -Werror=sequence-point -Werror=format-security -Wextra -pedantic -Wimplicit-fallthrough -Wsequence-point -Wswitch-unreachable -Wswitch-enum -Wstringop-truncation -Wbool-compare -Wtautological-compare -Wfloat-equal -Wshadow=global -Wpointer-arith -Wpointer-compare -Wcast-align -Wcast-qual -Wwrite-strings -Wdangling-else -Wlogical-op -Wconversion -g -O0
注:
按照 C17 标准编译
禁用了编译优化
启用了调试
初始化
调用 init() 会创建一条十个节点的链表,依次存储 0~9,创建的链表名为 head(全局变量)。调用 show_node() 可以查看 head 链表中的数据。
#include <stdlib.h>
#include <stdio.h>
typedef struct ListNode
{
int val;
void *next;
} ListNode;
#define N 10 // 创建链表长度,默认 10 即为 0~9
ListNode *head = NULL; // 全局链表
/**
* @brief 添加节点
*
* @param prev 上一个节点的地址,在其基础上添加新节点;传 NULL,则是创建独立的节点,如头部
* @param val 节点要插入的值
* @return ListNode* 返回新建节点的地址
*/
ListNode *add_node(ListNode *prev, int val)
{
ListNode *new_node = (ListNode *) malloc(sizeof(ListNode));
new_node->val = val;
new_node->next = NULL;
if (prev != NULL)
{
prev->next = new_node;
}
return new_node;
}
/**
* @brief 显示链表内容
*/
void show_node()
{
ListNode *ptr = head;
while (1)
{
printf("%d ", ptr->val);
ptr = ptr->next;
if (ptr == NULL)
{
break;
}
}
printf("\n");
}
/**
* @brief 初始化链表
* 共十个节点,依次存入 0~9
*
*/
void init()
{
head = add_node(NULL, 0);
ListNode *node_ptr = head;
for (int i = 1; i < N; ++i)
{
node_ptr = add_node(node_ptr, i);
}
}
实现一:迭代

ListNode *last = NULL;
ListNode *curr = NULL;
ListNode *next = head;
while (1)
{
last = curr;
curr = next;
next = next->next;
curr->next = last;
if (next == NULL)
{
head = curr;
break;
}
}

然后为了测试一下效率(数据量大,结果更为稳定),将链表长度的 N 值改为了一千万,并注释了 show_node() 调用,因为在数据量比较大的情况下,调用两千万次 printf(原链表和逆序后的链表)消耗的时间着实不少,而这个并不是算法本身需要消耗的时间。
测试结果也显现出来了,调用了 show_node() 的情况下使用了 25 秒左右的时间,注释后算法本身逆序只用了三百多毫秒。

实现二:递归
大体上和迭代相差不大,只是前一种使用的循环,这个是用的函数递归。

// 调用格式:
// head = recursion(NULL, head)
// 返回值 head 即为逆序后的头节点
ListNode *recursion(ListNode *prev, ListNode *curr);
ListNode *recursion(ListNode *prev, ListNode *curr)
{
if (curr == NULL)
{
return prev;
}
ListNode *next = curr->next;
curr->next = prev;
return recursion(curr, next);
}
递归相比迭代循环代码简洁一点,但是因为进程的栈空间有限,函数递归调用,层数不能太多,我电脑上测试了下链表节点数大概在17万到18万之间有个界限,超过之后运行就会出现段错误。

而且对比一下时间,17万个节点耗时就有40毫秒左右,前面迭代一千万个节点耗时也才三百多毫秒,相比而言还是迭代效率更高,而且支持更高的节点数。
单向链表反转
