反转链表:
示例: 输入: 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是一样的思想,只是一个循环一个递归而已。
以上就是我在写递归反转链表时思考的过程,如果有疑问,欢迎在评论区留言。
本人水平也有限,若有错误,也欢迎在评论区批评指正。