单链表翻转||链表和顺序表的优缺点

顺序表和链表的优缺点

  • 顺序表存储数据,需预先申请一整块足够大的存储空间,然后将数据按照次序逐一存储,数据之间紧密贴合,不留一丝空隙,如下图a) 所示;
  • 链表的存储方式与顺序表截然相反,什么时候存储数据,什么时候才申请存储空间,数据之间的逻辑关系依靠每个数据元素携带的指针维持,如下图b) 所示;
    在这里插入图片描述
开辟方式

顺序表一次开辟,永久使用(动态数组除外);
链表每次只申请一个节点,如果需要,后续继续申请

空间利用率

顺序表的空间利用率比链表高。(链表容易产生空间碎片)

空间碎片,指某些容量小于1kb以至无法得到有效利用的屋里空间
时间复杂度

解决不同类型的问题,顺序表和链表对应的复杂度不同

两类问题

  1. 问题中主要涉及访问元素的操作
  2. 问题中主要涉及元素的插入、删除和移动

第 1 类问题适合使用顺序表。这是因为,顺序表中存储的元素可以使用数组下标直接访问,无需遍历整个表,因此使用顺序表访问元素的时间复杂度为 O(1);而在链表中访问数据元素,需要从表头依次遍历,直到找到指定节点,花费的时间复杂度为 O(n);

第 2 类问题则适合使用链表。链表中数据元素之间的逻辑关系靠的是节点之间的指针,当需要在链表中某处插入或删除节点时,只需改变相应节点的指针指向即可,无需大量移动元素,因此链表中插入、删除或移动数据所耗费的时间复杂度为 O(1);而顺序表中,插入、删除和移动数据可能会牵涉到大量元素的整体移动,因此时间复杂度至少为 O(n);

单链表翻转

迭代翻转发(适合翻转带头结点的链表)

该算法的实现思想非常直接,就是从当前链表的首元节点开始,一直遍历至链表的最后一个节点,这期间会逐个改变所遍历到的节点的指针域,另其指向前一个节点。

具体的实现方法也很简单,借助 3 个指针即可。以图 1 中建立的链表为例,首先我们定义 3 个指针并分别命名为 beg、mid、end。它们的初始指向如下图 所示:

在这里插入图片描述

在上图的基础上,遍历链表的过程就等价为:3 个指针每次各向后移动一个节点,直至 mid 指向链表中最后一个节点(此时 end 为 NULL )。需要注意的是,这 3 个指针每移动之前,都需要做一步操作,即改变 mid 所指节点的指针域,另其指向和 beg 相同。

C语言代码实现

// 链表翻转
//1、迭代翻转法,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;
            if(end==NULL)
            {
                break;
            }
            //集体后移
            beg=mid;
            mid=end;
            end=end->next;
        }
        //最后修改头指针指向
        head=mid;
        return head;
    }
}
递归翻转

和迭代反转法的思想恰好相反,递归反转法的实现思想是从链表的尾节点开始,依次向前遍历,遍历过程依次改变各节点的指向,即另其指向前一个节点。

//递归翻转
link *recursive_reverse(link * head)
{
    if(head==NULL||head->next==NULL)  //空链或者只有一个节点,直接返回头指针
    {
        return head;
    }

    else
    {
        //一直递归,到最后一个节点
        link *new_head=recursive_reverse(head->next);

        //逐层退出时,new_head都不变,一直指向原链表中的最后一个节点
        //递归每退出一层,函数中的head指针都指向前一个结点

        //每退出一层,都需要改变head->next的结点指向,同时令head所指向结点为空
        head->next->next=head;
        head->next=NULL;

        return new_head;
    }
    

}
头插法翻转链表

在原有链表的基础上,依次将位于链表头部的结点摘下,然后采用头部插入的方式生成一个新链表

就地逆置法翻转链表

就地逆置法和头插法的实现思想类似,唯一的区别在于,头插法是通过建立一个新链表实现的,而就地逆置法则是直接对原链表做修改,从而实现将原链表反转。

//头插法翻转链表
//所谓头插法,是指在原有链表的基础上,依次将位于链表头部的节点摘下,然
//后采用从头部插入的方式生成一个新链表,则此链表即为原链表的反转版。

link * head_reverse(link * head)
{
    link *new_head=NULL;
    link *tmp=NULL;

    if(head==NULL||head->next==NULL)
    {
        return head;
    }
    while(head!=NULL)
    {
        tmp=head;
        //将tmp从head中摘除

        head=head->next;

        //将tmp插入到new_head节点中
        tmp->next=new_head;
        new_head=tmp;
    }
    return new_head;
}

//就地逆置法

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)
    {
        //将end从链表中删除
        beg->next=end->next;
        //将end移动到链表头
        end->next=head;
        head=end;
        //调整end指向,令其指向beg后一个节点,为翻转下一个节点做准备
        end=beg->next;
    }
}

总结

上面只是无头结点链表的例子,对于有头结点链表的翻转

  • 使用迭代反转法实现时,初始状态忽略头节点(直接将 mid 指向首元节点),仅需在最后一步将头节点的 next 改为和 mid 同向即可;
  • 使用头插法或者就地逆置法实现时,仅需将要插入的节点插入到头节点和首元节点之间即可;
  • 递归法并不适用反转有头结点的链表(但并非不能实现),该方法更适用于反转无头结点的链表。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值