文章目录

一、迭代法
1、定义
它通常基于一个初始值,然后按照特定的规则(一般通过循环结构来实现,比如 for 循环、 while 循环等)反复更新这个值,直到满足某个终止条件为止。这个终止条件可以是达到一定的精度要求、循环次数达到设定值等。
2、操作
ListNode* reverseList(ListNode* head) {
ListNode* prev = NULL;
ListNode* curr = head;
ListNode* nextTemp;
while (curr!= NULL) {
nextTemp = curr->next;
curr->next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
在这种方法中:
- 我们使用三个指针
prev
、curr
和nextTemp
。 prev
初始化为NULL
,它将用来指向反转后的链表的头节点。curr
初始化为链表的头节点head
,它是当前正在处理的节点。nextTemp
用于临时存储当前节点curr
的下一个节点,以便在反转当前节点的指针时不会丢失下一个节点的信息。- 通过不断地在循环中更新这三个指针的值,实现链表节点指针方向的反转,最后返回反转后的链表头节点
prev
。
二、递归法
1、定义
在C语言中,递归法求反转列表,就是利用函数自己调用自己的递归机制来改变链表(一种常见的可用于表示列表的数据结构,这里以链表为例来说明)中节点的顺序,将链表从头到尾的顺序进行反转,让原本的尾节点变成头节点,依次类推。
2、操作
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;
}
在递归法中:
- 首先判断链表的头节点
head
是否为空或者只有一个节点(即head->next == NULL
),如果是,则直接返回head
,因为这样的链表无需反转。 - 然后通过递归调用
reverseList(head->next)
来反转链表的剩余部分,得到反转后的链表头节点newHead
。 - 接着将当前节点
head
的下一个节点head->next
的下一个节点设置为head
,实现当前节点与反转后链表的连接。 - 最后将当前节点
head
的下一个节点设置为NULL
,完成当前节点在反转链表中的位置调整。 - 最终返回反转后的链表头节点
newHead
。
3、解释
1、递归终止条件判断
if (head == NULL || head->next == NULL) {
return head;
}
- 这里首先判断了两种情况作为递归的终止条件:
- 当
head
指针为NULL
时,说明链表为空链表,此时无需进行反转操作,直接返回NULL
即可,因为空链表反转后还是空链表。 - 当
head
的下一个节点指针head->next
为NULL
时,说明链表只有一个节点。对于只有一个节点的链表,也无需进行反转操作,直接返回该节点指针head
就行,因为单个节点的链表反转后还是它本身。
- 当
2. 递归调用反转剩余链表
struct ListNode* newHead = reverseList(head->next);
- 当不满足上述终止条件时,即链表至少有两个节点,此时函数会进行递归调用。它调用自身
reverseList
并传入当前头节点head
的下一个节点指针head->next
。 - 这个递归调用的目的是先反转当前节点
head
之后的链表部分。例如,对于链表1 -> 2 -> 3 -> 4
,当head
指向节点1
时,这里的递归调用会先去反转2 -> 3 -> 4
这部分链表,并返回反转后的头节点指针,将其存储在newHead
变量中。假设经过递归调用后,2 -> 3 -> 4
这部分链表反转成了4 -> 3 -> 2
,那么newHead
就指向节点4
。
3. 调整当前节点与反转后链表的连接关系
head->next->next = head;
- 在完成对当前节点
head
之后的链表部分的反转(通过递归调用得到了反转后的头节点newHead
)后,接下来需要将当前节点head
正确地连接到反转后的链表上。 - 这里通过
head->next->next = head
这一操作来实现连接。由于newHead
指向反转后的链表头节点(比如前面例子中的节点4
),那么head->next
就指向反转后链表中的下一个节点(比如节点3
),将head->next->next
设置为head
,就相当于把当前节点head
(比如节点1
)连接到了反转后链表的末尾,使得原本的4 -> 3 -> 2
变成了4 -> 3 -> 2 -> 1
。
4. 断开当前节点原有的后续连接
head->next = NULL;
- 在将当前节点
head
连接到反转后链表的末尾后,为了保证链表结构的正确性,需要断开当前节点head
与它原来后续节点的连接。 - 因为在前面的操作中已经将当前节点
head
重新连接到了反转后链表的合适位置,所以这里通过将head->next
设置为NULL
,就使得节点1
(前面例子中的当前节点head
)的下一个节点不再指向原来的节点2
,而是NULL
,从而完成了当前节点在反转链表中的位置调整,此时链表就完全反转成了4 -> 3 -> 2 -> 1
。
5. 返回反转后的链表头节点
- 最后,经过上述一系列操作,整个链表已经完成反转,此时通过返回
newHead
,就将反转后的链表头节点指针返回给调用者。在前面的例子中,newHead
指向节点4
,所以调用者接收到的就是反转后的链表4 -> 3 -> 2 -> 1
的头节点指针。
三、头插法(借助辅助头节点)
ListNode* reverseList(ListNode* head) {
ListNode* newHead = (ListNode*)malloc(sizeof(ListNode));
newHead->next = NULL;
ListNode* curr = head;
while (curr!= NULL) {
ListNode* nextTemp = curr->next;
curr->next = newHead->next;
newHead->next = curr;
curr = nextTemp;
}
ListNode* realHead = newHead->next;
free(newHead);
return realHead;
}
在头插法中:
- 首先创建一个辅助头节点
newHead
,并将其下一个节点设置为NULL
。 - 然后通过循环遍历原链表,对于原链表中的每个节点:
- 先临时保存当前节点
curr
的下一个节点nextTemp
。 - 再将当前节点
curr
的下一个节点设置为辅助头节点newHead
的下一个节点,实现将当前节点插入到辅助头节点之后的操作。 - 接着将辅助头节点
newHead
的下一个节点设置为当前节点curr
,完成头插操作。 - 最后将当前节点
curr
更新为刚才临时保存的下一个节点nextTemp
,以便处理下一个节点。
- 先临时保存当前节点
- 循环结束后,辅助头节点
newHead
的下一个节点就是反转后的链表,将其赋值给realHead
,然后释放辅助头节点newHead
,最后返回realHead
。
四、数组反转
struct ListNode* reverseList(struct ListNode* head) {
if (head == NULL) {
return NULL;
}
struct ListNode* temp1 = head;
int arr[5001], count = 0;
while (temp1 != NULL) {
arr[count++] = temp1->val;
temp1 = temp1->next;
}
count-=1;
struct ListNode* temp2 = head;
while (temp2 != NULL) {
temp2->val = arr[count--];
temp2 = temp2->next;
}
return head;
}