单链表的反转(递归)

单项链表的反转指的是这样一类问题:给定一个单项链表的头指针 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程序;

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值