单链表操作——详解递归法反转链表(有图)

反转链表:
示例: 输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

关于递归法,我会把我在写的时候是怎么一步步思考的完整的展现出来,各位码友可以顺着思路走一遍,相信捋完后会形成自己的理解。好,我们开始吧

在讲解之前,我定义了一个结点类和链表类,如下:

template <typename T>
class Mylist;

//定义链表的结点类
template <typename T>
class ListNode
{
	friend class Mylist<T>;//Mylist类为ListNode类的友元,从而可以访问其私有成员

private:
	T data;
	ListNode<T> * link;

	ListNode() :link(nullptr){};
	ListNode(T _data):data(_data),link(nullptr){}
};


//定义链表类
template <typename T>
class Mylist
{
public:
	Mylist();
	~Mylist();

	void Insert(const T& _data);//插入数据
	void Invert();//反转链表

	void Show();//显示链表
private:
	ListNode<T> * head;
};

确定递归的具体任务
我首先想到的是,既然是交换反转链表,每一步的任务可以有两种情况

  • 一直递归到最后一个结点,然后在返回时改变结点的指向
  • 每一次的递归都先改变结点的指向,返回过程不做任何事情

第一种情况肯定不行,因为无法返回反转后链表的头结点

所以只能第二种情况。
第二种情况只需把当前结点(current)的下一个结点(next)的指向改为指向当前结点,这样就反转了current和next两个结点。每次的操作都是两个结点之间。如下图。
在这里插入图片描述

这样,就有如下代码:

ListNode<T> *Invert(ListNode<T> * current)
{
	if (current == nullptr){
		return current;
	}
	next = current->link; 
	next ->link = current; 
	return Invert(next);
}

这样会有什么问题呢?如下图,我们已近改变了next结点的指向。如果我们递归调用,即return Invert(next);我们无法找到下一个结点,即node结点。
在这里插入图片描述
既然我必须要node结点的地址,所以我考虑增加函数的一个参数,它就表示node结点。所以现在我会传入两个参数,一个是current,一个是current的下一个结点next,就可以写出如下代码:

template <typename T>
ListNode<T> * Mylist<T>::Invert(ListNode<T> * current, ListNode<T> *next)
{
	if (next == nullptr){
		return current;
	}
	ListNode<T> * tmp = next->link;//保存node结点的地址

	next->link = current;//next指向current

	current = next;//current现在应该为next
	next = tmp;//next现在为node
	return Invert(current, next);//递归调用
}

写到这里,我测试了一下,发现可以实现链表的反转,表明代码是没有问题的。现在把测试代码给出来:
MyList.h

#ifndef _MY_LIST_H
#define _MY_LIST_H

template <typename T>
class Mylist;

//定义链表的结点类
template <typename T>
class ListNode
{
	friend class Mylist<T>;//Mylist类为ListNode类的友元,从而可以访问其私有成员

private:
	T data;
	ListNode<T> * link;

	ListNode() :link(nullptr){};
	ListNode(T _data):data(_data),link(nullptr){}
};


//定义链表类
template <typename T>
class Mylist
{
public:
	Mylist();
	~Mylist();

	void Insert(const T& _data);//插入数据
	void Invert();//反转链表
	ListNode<T> * Invert(ListNode<T> * current, ListNode<T> *next);//反转链表

	void Show();//显示链表
private:
	ListNode<T> * head;
};
//构造和析构函数
template <typename T>
Mylist<T>::Mylist()
:head(nullptr)
{
}
template <typename T>
Mylist<T>::~Mylist()
{
	while (head != nullptr){
		ListNode<T> *tmpHead = head->link;
		delete head;
		head = tmpHead;
	}
}

//插入数据
template <typename T>
void Mylist<T>::Insert(const T& _data)
{
	ListNode<T> *newNode = new ListNode<T>(_data);//生成一个新的结点
	newNode->link = head;//新结点指向原来的头结点
	head = newNode;//让头结点指向新结点,因为新插入的结点就是头结点
}

//反转链表
template <typename T>
void Mylist<T>::Invert()
{
	//原始方法:
	/*ListNode<T> * tmp = head->link;
	head->link = nullptr; //这里必须把head的link指向NULL,因为反转后就是最后一个结点
	head = Invert(head,tmp);*/
	
	//简便方法:
	head = Invert(nullptr, head);
}


template <typename T>
ListNode<T> * Mylist<T>::Invert(ListNode<T> * current, ListNode<T> *next)
{
	if (next == nullptr){
		return current;
	}
	ListNode<T> * tmp = next->link;//保存node结点的地址

	next->link = current;//next指向current

	current = next;//current现在应该为next
	next = tmp;//next现在为node
	return Invert(current, next);//递归调用
}

//显示链表
template <typename T>
void Mylist<T>::Show()
{
	for (ListNode<T>* current = head; current != 0; current = current->link){
		std::cout << current->data;
		if (current->link) std::cout << "->";
		else std::cout << std::endl;
	}
}

#endif

main.cpp

#include <iostream>
#include <list>
#include "MyList.h"

using namespace std;

int main()
{
	Mylist<int> l;

	l.Insert(1);
	l.Insert(2);
	l.Insert(3);
	l.Insert(4);
	l.Insert(5);
	l.Show();
	l.Invert();
	l.Show();


	system("pause");
	return 0;
}

测试结果:
在这里插入图片描述

总结:反转链表的递归法,就是利用两个指针,一个指向当前结点,一个指向后一个结点,然后不断向后移动,该变指向的过程。其实从本质上讲,它和我在单链表操作——非递归方法反转链表的两种方法(有图)中讲解的方法1是一样的思想,只是一个循环一个递归而已。

以上就是我在写递归反转链表时思考的过程,如果有疑问,欢迎在评论区留言。
本人水平也有限,若有错误,也欢迎在评论区批评指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值