看图深入理解单链表的反转

一、简述

如何把一个单链表进行反转?

方法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;
}

输出结果如下图所示:


参考:

看图深入理解单链表的反转


posted @ 2019-09-22 16:18  fengMisaka  阅读(1115)  评论(2编辑  收藏  举报