如何实现单链表的反转-无头节点


前言

所谓反转链表,就是将链表整体“反过来”,将头变成尾、尾变成头。那么,如何实现链表的反转呢?

常用的实现方案有 4 种,这里分别将它们称为迭代反转法、递归反转法、就地逆置法和头插法

值得一提的是,递归反转法更适用于反转不带头节点的链表;其它 3 种方法既能反转不带头节点的链表,也能反转带头节点的链表。

注意:以下均以不带头节点的链表为例讲解。


一、迭代反转链表

在这里插入图片描述
步骤:

  1. 需要设置三个指针:beg, mid, end,分别指向如图所示位置;
  2. 改变mid的指向,将mid指向beg;
  3. 三个指针均向前一步;
  4. 重复2.3步骤,直到end指向NULL;
  5. 改变mid的指向,将mid指向beg;
  6. 最后只需改变 head 头指针的指向,另其和 mid 同向,就实现了链表的反转。

c语言实现代码如下:

//迭代反转法,head 为无头节点链表的头指针
link * iteration_reverse(link* head){
    if (head == NULL || head->next == NULL) {
        return head;
    }
    else{
        link * beg = NULL;
        link * mid = head;
        link * end = head->next;
        //遍历
        while(1){
            //修改 mid 所指节点的指向
            mid->next = beg;
            //此时判断 end 是否为 NULL,如果成立则退出循环
            if (end == NULL) {
                break;
            }
            beg = mid;
            mid = end;
            end = end->next;
        }
        //最后修改head指向
        head = mid;
        return head;
    }
}

二、递归反转链表

和迭代反转法的思想恰好相反,递归反转法的实现思想是从链表的尾节点开始,依次向前遍历,遍历过程依次改变各节点的指向,即另其指向前一个节点。
在这里插入图片描述
c语言实现代码如下:

//递归反转
link* recursive_reverse(link* head) {
    //递归的出口
    if (head == NULL || head->next == NULL)     // 空链或只有一个结点,直接返回头指针
    {
        return head;
    }
    else{
        //一直递归,找到链表中最后一个节点
        link *new_head = recursive_reverse(head->next);
        
        head->next->next = head;
        head->next = NULL;
        return new_head;
    }
}

步骤:

  1. 递归的"深入"阶段 - 就像剥洋葱

想象你有一串珍珠项链(链表),要从头到尾把每颗珍珠都翻转方向。递归的做法是:

先找到最后一颗珍珠(链表尾节点)
然后从后往前一颗一颗翻转

这个过程就像:

你问第一个人:"后面的人反转过来了吗?"
第一个人问第二个人:"后面的人反转过来了吗?"
...
最后一个人说:"我是最后一个,不用反转了"(递归终止条件)

  1. 递归的"回溯"阶段 - 从后往前处理

当递归到达最后一个节点后,开始往回走(回溯),这时候每退回一层就会执行那两行关键代码:

head->next->next = head;  // 让下一个节点指向自己
head->next = NULL;        // 断开自己原来的指向

这就像:

最后一个人告诉前一个人:"我已经反转好了,现在该你了"
前一个人调整自己的指针方向
然后告诉更前一个人:"我们俩已经反转好了,现在该你了"
...
一直传递到第一个人

  1. 为什么这两行代码会执行多次?

因为每一层递归在返回时都会执行它们!比如链表有5个节点:

递归到第5个节点时停止(满足终止条件)
返回到第4个节点时执行这两行代码(处理4和5的关系)
返回到第3个节点时执行这两行代码(处理3和4的关系)
返回到第2个节点时执行这两行代码(处理2和3的关系)
返回到第1个节点时执行这两行代码(处理1和2的关系)

每一层递归都有自己的head变量(指向不同节点),所以每次执行这两行代码时,head的值都不同,处理的是不同位置的节点关系。


  1. 通俗比喻

想象你在一个有很多房间的走廊里,每个房间都有一个开关和一个指向下一个房间的箭头:

你从第一个房间出发,沿着箭头走到最后一个房间(递归深入)
在最后一个房间,你发现没有下一个房间了(递归终止)
你开始往回走,每到一个房间就做两件事:
    把下一个房间的箭头指向当前房间(反转方向)
    把当前房间的箭头关掉(设为NULL)
这样走完全部房间后,所有箭头方向都反过来了

这个过程中,你没有使用循环语句,但通过"走到尽头再回头处理"的方式,实现了对每个房间(节点)的操作,这就是递归的"循环"效果。


可视化过程:

初始链表:1 → 2 → 3 → NULL
递归调用:
reverseList(1)
reverseList(2)
reverseList(3) → 返回3
reverseList(2)执行:3→2 2→NULL 返回3
reverseList(1)执行:2→1 1→NULL 返回3
最终链表:3 → 2 → 1 → NULL


三、头插法反转链表

头插法是指依次拿下链表头部的节点,采用头部插入的方法构建一个新链表,即为原链表的反转链表。
新反转链表的头指针为 new_head。

//头插法反转
link * head_reverse(link * head) {
    link * new_head = NULL;
    link * temp = NULL;
    if (head == NULL || head->next == NULL) {
        return head;
    }
    while(head != NULL){
        temp = head;
        head = head->next;  // 将temp从head摘除

        // 将temp插入new_head新链表
        temp->next = new_head;
        new_head = temp;
    }
    return new_head;
}

流程如下:
【初始状态】
原链表:
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│头 │───→│ 1 │───→│ 2 │───→│ 3 │───→│ 4 │───→│ 5 │───→NULL
└───┘ └───┘ └───┘ └───┘ └───┘ └───┘

新链表:
┌───┐
│new│───→NULL
└───┘

【第1步】摘下节点1
原链表:
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│头 │───→│ 2 │───→│ 3 │───→│ 4 │───→│ 5 │───→NULL
└───┘ └───┘ └───┘ └───┘ └───┘

新链表:
┌───┐ ┌───┐
│new│───→│ 1 │───→NULL
└───┘ └───┘

【第2步】摘下节点2
原链表:
┌───┐ ┌───┐ ┌───┐ ┌───┐
│头 │───→│ 3 │───→│ 4 │───→│ 5 │───→NULL
└───┘ └───┘ └───┘ └───┘

新链表:
┌───┐ ┌───┐ ┌───┐
│new│───→│ 2 │───→│ 1 │───→NULL
└───┘ └───┘ └───┘

【第3步】摘下节点3
原链表:
┌───┐ ┌───┐ ┌───┐
│头 │───→│ 4 │───→│ 5 │───→NULL
└───┘ └───┘ └───┘

新链表:
┌───┐ ┌───┐ ┌───┐ ┌───┐
│new│───→│ 3 │───→│ 2 │───→│ 1 │───→NULL
└───┘ └───┘ └───┘ └───┘

【第4步】摘下节点4
原链表:
┌───┐ ┌───┐
│头 │───→│ 5 │───→NULL
└───┘ └───┘

新链表:
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│new│───→│ 4 │───→│ 3 │───→│ 2 │───→│ 1 │───→NULL
└───┘ └───┘ └───┘ └───┘ └───┘

【第5步】摘下节点5
原链表:
┌───┐
│头 │───→NULL
└───┘

新链表(最终结果):
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│new│───→│ 5 │───→│ 4 │───→│ 3 │───→│ 2 │───→│ 1 │───→NULL
└───┘ └───┘ └───┘ └───┘ └───┘ └───┘


四、就地逆置法反转链表

与头插法反转链表不同的是,就地置法反转链表不需要建立新的链表,在原来的基础上即可完成反转的操作,需要设置两个指针来进行操作。
步骤如下:

  1. 初始准备:设定两个指针
    beg:指向链表的头节点(或首元节点,根据实现决定)
    end:指向beg的下一个节点(即待反转的第一个节点)

  2. 移除end节点

    将beg的next指针跳过end,直接连接到end的下一个节点 (此时end节点被移出原链表)

  3. 将end节点插入链表头部

    让end的next指针指向当前的头节点 (此时end成为新的头节点)

  4. 更新头节点指针

    将head指针重新指向end(即新的头节点) (确保后续操作能继续从头部插入)

  5. 调整end指针

    将end重新指向beg的下一个节点 (准备处理下一个待反转的节点)

完整的c语言代码:

//就地反转
link * local_reverse(link * head) {
    link * beg = NULL;
    link * end = NULL;
    if (head == NULL || head->next == NULL) {
        return head;
    }

    beg = head;
    end = head->next;
    while(end != NULL){
        beg->next = end->next;  // 将end从链表移除
        end->next = head;  //将end移动到链表头部
        head = end;
        end = beg->next;  //调整 end 的指向,另其指向 beg 后的一个节点,为反转下一个节点做准备 
    }
    return head;
}


总结

以上就是今天要讲的内容,本文介绍了如何实现无头节点的单链表的反转,涉及到4种方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值