反转链表-----头插法/尾插法/递归

一 链表简介

链表是一种数据结构,它包括物理结构和逻辑结构;在物理结构上它表现为非连续、非顺序的性质,然而在逻辑上链表是一种顺序结构。它解决顺序表(简单理解为C语言数组)调整大小带来的数据复制问题,可以实现在O(1)的时间范围内实现插入和删除,但也失去了顺序表的随机访问特性。

其结构包括一个数据域和指针域,数据区域用于保存数据,指针域用于保存下一个元素的地址,C语言定义如下:

typedef struct Node
{
    int data;//数据域
    struct Node* next;//指针域
}*linklist,Node;

用图示表现链表为:

这里对链表不做过多介绍,需要对链表详细了解,请参考博客:

【数据结构】链表(单链表实现+详解+原码)-CSDN博客

二 反转链表

2.1 问题引入

给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。

示例如下:

题目来源力扣:LCR 024. 反转链表 - 力扣(LeetCode)

其节点的定义如下:

/*Definition for singly-linked list.*/
 struct ListNode {
     int val;
     ListNode *next;
     ListNode() : val(0), next(nullptr) {}
     ListNode(int x) : val(x), next(nullptr) {}
     ListNode(int x, ListNode *next) : val(x), next(next) {}
 };

2.2 简单分析----利用数组/顺序表

首先我们遍历一遍链表,并将每个节点放入到已经定义好的数组Nodes中,可以得到数组Nodes如下所示:

然后我们可以将数组Nodes从第n个元素开始,依次使其next区域指向前一个元素,数组第一个元素的next指向NULL(如下图所示,蓝色箭头),返回数组最后一个元素,即完成链表反转。

代码实现(c++)

 ListNode* reverseList(ListNode* head) 
 {
     if(head == nullptr) return head;
     vector<ListNode*>Nodes;
     ListNode* p = head;
     while(p)
     {
         Nodes.push_back(p);
         p = p->next;
     }
     int n = Nodes.size();
     for(int i = n-1;i > 0;i --)
         Nodes[i]->next = Nodes[i-1];
     Nodes[0]->next = nullptr;//修改第一元素的next为NULL
     
     return Nodes[n-1];
 }

复杂度分析(假设链表大小为n):

时间复杂度:O(n),遍历两遍

空间复杂度:O(n),数组大小

2.3 利用栈和尾插法

创建栈stk,利用栈存储先进后出的特性,将节点全部存入后,依次取出进行尾插法,完成链表反转。

其过程如下图所示:

直至栈为空,此时原来的链表已经反转,且头节点为L

代码实现:

 ListNode* reverseList(ListNode* head) 
 {
     if(head == nullptr) return head;
     stack<ListNode*>stk;
     ListNode* p = head;
     while(p)
     {
         stk.push(p);
         p = p->next;
     }
     ListNode* L = new ListNode(-1);//新建头节点
     p = L;//尾指针
     while(!stk.empty())
     {
         ListNode* curr = stk.top();
         stk.pop();
         curr->next = nullptr;
         p->next = curr;
         p = curr;
     }
     p =  L->next; 
     delete L;
     return p;
 }

复杂度分析(假设链表大小为n):

时间复杂度:O(n),遍历两遍

空间复杂度:O(n),栈空间大小

2.4 “优雅”遍历---递归

有了2.3的思路,自然可以想到递归的压栈操作,可以先利用递归遍历,直到发现其next为空,即最后一个节点n,那么则开始将该节点使用尾插法插入到准好的新建链表之中,之后递归返回到第n-1个节点,重复上述操作,其过程与栈类似。

如下图所示:

代码实现:

ListNode* l;
ListNode* p;
void func(ListNode* head)
{
    if(head == NULL) return ;
    func(head->next);
    //返回后开始尾插
    head->next = nullptr;
    p->next = head;
    p =head;
}
ListNode* reverseList(ListNode* head) 
{
        l = new ListNode(-1);
        p = l;
        func(head);
        p = l->next;
        delete l;
        return p;
}

复杂度分析(假设链表大小为n):

时间复杂度:O(n),遍历一遍

空间复杂度:O(n),递归的深度

2.5 快速反转/头插法(原地反转)

在此方法中,我们事先建立一个新的链表L,然后遍历原来的链表的每一个节点,用curr指针来指向当前节点,然后将curr节点从原来的链表中摘下来,并且插入到L的next(利用头插法逆序,可参考前面博客看头插详细)

具体代码如下:

ListNode* reverseList(ListNode* head) 
{
    if(head == NULL) return head;
    
    ListNode* L = new ListNode(-1);//事先建立的链表
    
    ListNode* curr = head;
    
    while(curr)
    {
        ListNode* Temp = L->next;
        
        L->next =  curr;
        
        curr = curr->next;
        
        L->next->next = Temp;
    }
    curr = L->next;
    delete L;
    
    return curr;
}

复杂度分析(假设链表大小为n):

时间复杂度:O(n),遍历一遍

空间复杂度:O(1),没有使用额外的空间

谢谢你的观看,如有错误,欢迎指正!!!

如果对你有帮助,点赞、收藏 + 关注,谢谢

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值