链表逆置就是把最后一个数据提到最前面,倒数第二个放到第二个……依次类推,直到第一个到最后一个。
由于链表没有下标,所以不能借助下标来实行数据的逆置,要靠空间的转移来完成链表的逆置,这里采用没有头节点的链表来实现逆置。
0.链表结构:
struct ListNode {
int val;
struct ListNode* next;
};
1.头插法:
算法思想:逆置链表(新链表)初始为空,表中节点从原链表中依次“删除”,再逐个插入逆置链表的表头(即“头插”到逆置链表中),使它成为逆置链表的“新”的第一个结点,如此循环,直至原链表为空。
struct ListNode* reverseList1(struct ListNode* head) {
struct ListNode* tmp = NULL, * cur = NULL;
tmp = head;
//判空或单结点
if (tmp == NULL || tmp->next == NULL) {
return head;
}
head = head->next;
tmp->next = NULL;
//tmp为新链表
while (head) {
cur = head;
head = head->next;
cur->next = tmp;
tmp = cur;
}
head = tmp;
return head;
}
2.就地逆置:
算法思想:就地逆置法和头插法的实现思想类似,唯一的区别在于,头插法是通过建立一个新链表实现的,而就地逆置法则是直接对原链表做修改,从而实现将原链表反转。在原链表的基础上做修改,需要额外借助 2 个指针(假设分别为 p和 q)。
struct ListNode* reverseList2(struct ListNode* head) {
struct ListNode* tmp = NULL, * cur = NULL;
tmp = head;
//判空或单结点
if (tmp == NULL || tmp->next == NULL) {
return tmp;
}
cur = tmp->next;
head->next = NULL;
head = tmp;
//就地逆置
while (cur) {
tmp = cur;
cur = cur->next;
tmp->next = head;
head = tmp;
}
return head;
}
3.迭代:
在遍历链表时,将当前节点的next指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。
struct ListNode* reverseList3(struct ListNode* head) {
struct ListNode* tmp = NULL, * cur = head;
while (cur) {
struct ListNode* next = cur->next;
cur->next = tmp;
tmp = cur;
cur = next;
}
return tmp;
}
4.递归:
递归版本稍微复杂一些,其关键在于反向工作;若结点head->next及以后结点都已经逆置,我们希望head->next下一个指向的结点是head,所以:head->next->next=head;head->next=NULL;
struct ListNode* reverseList4(struct ListNode* head) {
if (head == NULL || head->next == NULL) {
return head;
}
struct ListNode* tmp = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return tmp;
}