看图深入理解单链表的反转
一、简述
如何把一个单链表进行反转?
方法1:将单链表储存为数组,然后按照数组的索引逆序进行反转。
方法2:使用 3 个指针遍历单链表,逐个链接点进行反转。
方法3:从第 2 个结点到第 N 个结点,依次逐结点插入到第 1 个结点(head 结点)之后,最后将第一个结点挪到新表的表尾。
方法4: 递归。
这里我们重点介绍方法2,先看实现代码:
// 链表反转操作
LinkList* ReverseList(LinkList* head)
{
// 少于两个结点没有反转的必要
if (NULL == head || NULL == head->next)
return head;
LinkList* p; // 当前结点的上一个结点
LinkList* q; // 当前结点
LinkList* r; // 保存下一步要处理的指针
p = head;
q = head->next; // 当前结点为头结点的下一个结点
head->next = NULL; //旧的头指针是新的尾指针,next需要指向NULL
while (q)
{
r = q->next; // 先保存下一步要处理的指针
q->next = p; // 然后p q交替工作进行反向
// 指针向后移动
p = q;
q = r;
}
head = p; // 最后q必然指向NULL,所以返回了p作为新的头指针
return head;
}
二、图解
下面看图来理解单链表的反转。
使用 p 和 q 两个指针配合工作,使得两个节点间的指向反向,同时用 r 记录剩下的链表。
LinkList* p; // 当前结点的上一个结点
LinkList* q; // 当前结点
LinkList* r; // 保存下一步要处理的指针
现在进入循环体,这是第一次循环。
r = q->next; // 先保存下一步要处理的指针
q->next = p; // 然后p q交替工作进行反向
// 指针向后移动
p = q;
q = r;
第二次循环。
r = q->next; // 先保存下一步要处理的指针
q->next = p; // 然后p q交替工作进行反向
// 指针向后移动
p = q;
q = r;
后面依次类推,最后 p 指向尾节点,而 q 指向NULL,结束循环。
三、完整示例代码
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
typedef int Status; // Status是函数结果状态,成功返回TRUE,失败返回FALSE
typedef int ElemType;
/* 线性表的单链表存储结构 */
typedef struct node
{
ElemType data;
struct node *next;
}Node, LinkList;
void initList(LinkList **pList); // 初始化链表操作
Status insertListTail(LinkList *pList, const ElemType e); // 尾部后插入元素操作
LinkList* reverseList(LinkList* head); // 链表反转操作
void traverseList(LinkList *pList); // 遍历链表操作(也遍历了头结点)
// 初始化单链表操作
void initList(LinkList **pList) // 必须使用双重指针,一重指针申请会出错
{
*pList = (LinkList *)malloc(sizeof(Node));
if (!pList)
{
printf("malloc error!\n");
return;
}
(*pList)->data = 0;
(*pList)->next = NULL;
}
// 尾部后插入元素操作
Status insertListTail(LinkList *pList, const ElemType e)
{
Node *cur;
Node *temp;
// 判断链表是否存在
if (!pList)
{
printf("list not exist!\n");
return FALSE;
}
// 找到链表尾节点
cur = pList;
while (cur->next)
{
cur = cur->next;
}
// 创建存放插入元素的结点
temp = (Node *)malloc(sizeof(Node));
if (!temp)
{
printf("malloc error!\n");
return -1;
}
temp->data = e;
// 尾结点后插入结点
temp->next = cur->next;
cur->next = temp;
return TRUE;
}
// 链表反转操作
LinkList* reverseList(LinkList* head)
{
// 少于两个结点没有反转的必要
if (NULL == head || NULL == head->next)
return head;
LinkList* p; // 当前结点的上一个结点
LinkList* q; // 当前结点
LinkList* r; // 保存下一步要处理的指针
p = head;
q = head->next; // 当前结点为头结点的下一个结点
head->next = NULL; //旧的头指针是新的尾指针,next需要指向NULL
while (q)
{
r = q->next; // 先保存下一步要处理的指针
q->next = p; // 然后p q交替工作进行反向
// 指针向后移动
p = q;
q = r;
}
head = p; // 最后q必然指向NULL,所以返回了p作为新的头指针
return head;
}
// 遍历链表操作(也遍历了头结点)
void traverseList(LinkList *pList)
{
// 判断链表是否存在
if (!pList)
{
printf("list not exist!\n");
return;
}
Node *cur = pList;
while (cur != NULL)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
int main()
{
LinkList *pList;
// 初始化链表
initList(&pList);
printf("初始化链表!\n\n");
// 尾部后插入元素
insertListTail(pList, 1);
printf("尾部后插入元素1\n\n");
insertListTail(pList, 2);
printf("尾部后插入元素2\n\n");
insertListTail(pList, 3);
printf("尾部后插入元素3\n\n");
// 反转前遍历链表(也遍历了头结点)
printf("反转前遍历链表(也遍历了头结点):");
traverseList(pList);
printf("\n");
// 链表反转
pList = reverseList(pList);
// 反转后遍历链表(也遍历了头结点)
printf("反转后遍历链表(也遍历了头结点):");
traverseList(pList);
printf("\n");
return 0;
}
输出结果如下图所示:
参考: