您的位置:首页 > 其它

单链表的反转(递归)

2016-05-22 10:15 323 查看
单项链表的反转指的是这样一类问题:给定一个单项链表的头指针 head,写一个算法,将其反转,并返回新的头指针 newHead。

注意点:

(1)一般我们构建的单链表有两种,第一种是有头节点的单链表,头指针指向头节点,第二种是没有头节点的单链表,头指针指向链表的首元节点;

(2)区分:头节点、首元节点、头指针的关系

头节点:头节点位于首元节点之前,头节点的存在是为了方便操作链表,此时因为单链表有头节点,所以头指针指向头节点;

首元节点:单链表有头节点时,首元节点就是紧跟着头节点之后的那个节点。若单链表没有头节点,则首元节点此时就是链表的开始节点(第一个),此时头指针指向首元节点;

(3)上一片博客 单链表的反转(非递归)中已经对链表的反转做了简要的介绍,并且对有/无头节点的链表的反转都做了分析。其实,同一种反转方法对于有/无头节点的链表来说,是没有多大区别的,可以将方法统一为“不带头节点的单链表反转”,使用“不带头节点的单链表的反转方法”来反转“带头节点的单链表”时,只需要取出头节点之后的那个节点(head->next)作为参数传入方法即可,最后再将该头节点插入到反转后链表的首元节点之前。

(4)无论链表有没有头节点,反转链表,反转的是“不包含头节点的那部分链表”。

链表的递归反转,也有多重实现方法:

思路分为2种:

第一种:原地反转;

第二种:非原地反转;

其中非原地反转又有2种实现方式:

(1)只含有一个参数的递归方法

(2)含有2个参数的递归方法(根据参数的特点,又稍有不同)

具体实现见代码:

#include <iostream>

typedef struct node
{
int data;
struct node *next;
} NODE;

// 尾插法创建单链表(带头节点)
NODE *createEnd(int arr[], int len)
{
NODE *head = (NODE *)malloc(sizeof(NODE)); // 生成头节点
head->next = NULL;
NODE *end = head;  // 尾指针初始化

for (int i = 0; i < len; i++) {

NODE *p = (NODE *)malloc(sizeof(NODE)); // 为每个数组元素建立一个节点
p->data = arr[i];
end->next = p;  // 将节点p插入到终端节点之后
end = p;
}

end->next = NULL;  // 单链表建立完毕,将终端节点的指针域置空

return head;
}

// ----------------------------------------------------------------------------------------------------

// 1、非原地反转(两个参数)
// 如果不要求“原地”,正向遍历原链表,头插法建立一个新的单向链表,它就是原链表的逆序。
// 该方法对于“带/不带头节点的单链表”来说的都适用,但是对于带头节点的单链表来说,在使用时要注意参数的传递,以及最后对头节点的处理;(详见代码)
NODE* reverse1(NODE *first, NODE *second)
{
if (second == NULL)
return first;

NODE *third = second->next;
second->next = first;
return reverse1(second, third);
}

// 2、原地反转(一个参数)
// 该方法对于“带头节点的单链表”来说,要注意参数传递的特点
// 递归终止条件就是链表只剩一个节点时直接返回这个节点的指针。可以看出这个算法的核心其实是在回朔部分,递归的目的是遍历到链表的尾节点,然后通过逐级回朔将节点的next指针翻转过来。
NODE *reverse2(NODE *head)
{
if(head == NULL || head->next == NULL)
return head;

NODE * newhead = reverse2(head->next); // 先反转后面的链表
head->next->next = head;//再将当前节点(head)设置为其然来后面节点(head->next)的后续节点
head->next = NULL;
return newhead; // 此处返回的newhead,永远指向反转后链表的首元节点,不随着回朔而变化。
}

// 3、原地反转(二个参数)
NODE *reverse3(NODE *head, NODE *& newHead)
{
if (head == NULL || head->next == NULL) {
newHead = head;
return head;
}

NODE *new_tail = reverse3(head->next, newHead);
new_tail->next = head;
head->next = NULL;
return head;
}

// 4、原地反转(二个参数)(和3比较,不同点是没有新增一个引用参数)(该方法更适合不带头节点的链表,对于带头节点的链表要稍作处理)
NODE *reverse4(NODE *p,NODE *head)
{
if(p->next==NULL)
{
head=p;
return head;
}
NODE *newHead = reverse4(p->next,head);
p->next->next=p;//反转节点
p->next=NULL;//第一个节点反转后其后继应该为NULL
return newHead;
}

// 5、该方法和方法4其实是一样的,写在此处仅仅是为了和方法4做对比,说明带头节点的单链表,在不作处理的时候,能直接使用的方法(适合有头节点的链表)
NODE *reverse5(NODE *p,NODE *head)
{
if(p->next==NULL)
{
head->next=p; // 仅此处和方法4不同
return head;
}
NODE *newHead = reverse5(p->next,head);
p->next->next=p;//反转节点
p->next=NULL;//第一个节点反转后其后继应该为NULL
return newHead;
}

// -----------------------------------------------------------------------------------------------------

// 单链表打印
void print(NODE *head)
{
if (head == NULL) return;

NODE *p = head->next;
while (p != NULL) {
printf("%d\n", p->data);
p = p->next;
}
}

int main(int argc, const char * argv[]) {

int arr[] = {1,2,3,4,5,6,7};
int len = sizeof(arr)/sizeof(int);

NODE *head = createEnd(arr, len);

print(head);

printf("------反转后-----\n");

/*--------------------- 可分别打开(1~5)的注释,来查看链表反转后的打印-----------------------------*/

// 1、reverse1  (已知创建的是有头节点的单链表)(注释读起来可能有点绕人,所以一定要清晰:头节点、首元节点、头指针的区别)
//    NODE *first = head->next; // 首元节点
//    NODE *second = head->next->next; // 第二个节点
//    head->next->next = NULL; // 断开首元节点和头节点之间的连接,同时,此时的首元节点是反转后链表的尾节点,指向NULL。
//    NODE *new_head = reverse1(first, second); // new_head指向反转后的链表的首元节点
//    head->next = new_head; // 重新让(原来的头节点)作为新链表的头节点。
//    print(head);

// 2、reverse2  (已知创建的是有头节点的单链表)
//    NODE *new_head = reverse2(head->next); // 传递的参数是原链表的首元节点
//    head->next = new_head; // 重新让(原来的头节点)作为新链表的头节点。
//    print(head);

// 3、reverse3  (已知创建的是有头节点的单链表)
//    NODE *new_head = NULL;
//    NODE *first = head->next; // 原链表的首元节点
//    reverse3(first, new_head); // 传入首元节点
//    head->next = new_head; // 重新让(原来的头节点)作为新链表的头节点。
//    print(head);

// 4、reverse4
//    NODE *first =head->next; // 原链表的首元节点
//    NODE *new_head = reverse4(first, head);
//    head->next = new_head; // 重新让(原来的头节点)作为新链表的头节点。(和方法5比较,此处是必须要做的)
//    print(head);

// 5、reverse5
//    NODE *first =head->next;
//    NODE *new_head = reverse5(first, head);
//    print(new_head);

return 0;
}


知识点复习:

(1)引用类型是C++提供的,C语言之所以能够使用是因为其使用的C++编译器且源文件后缀是 .cpp。

(2) cpp即C++(C Plus Plus),是C++程序的源文件。若是将源文件后缀改为.c,则引用类型是不可以使用的;

(3)由于C++对C几乎完全兼容,所以大多数人们都选用C++编译器来写C程序;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: