C语言实现单链表---面试题详解

一、比较顺序表和链表的优缺点,说说他们分别在什么场景下应用

空间上的比较(Space)

a. 空间的开辟:
顺序表的实现一般是实现连续开辟一段空间(或根据需求动态连续开辟空间),然后在进行数据的增删查改(静态顺序表),所以顺序表一般是固定空间大小的;
单链表则是一次只开辟一个结点的空间,用来存储当前要保存的数据及指向下一个结点或NULL的指针,所以单链表的空间大小时动态变化的。

b. 空间的使用:
当我们不知道要存储多少数据时,用顺序表来开辟的空间如果太大,就会造成一定程度上的浪费。
用单链表是实现时,因为是每需要存储一个数据时,才开辟一个空间,虽然有非数据项的指针占空间,但相比顺序表来说,浪费不是那么明显。(因为链表每次都是开辟的位置都是随机的,那么可能会把这块空间搞得七零八碎,出现很多小的一般使用不到的碎片空间)

c. 对CPU高速缓存的影响:
顺序表的空间一般是连续开辟的,而且一次会开辟存储多个元素的空间,所以在使用顺序表时,可以一次把多个数据写入高速缓存,再写入主存,顺序表的CPU高速缓存效率更高,且CPU流水线也不会总是被打断;
单链表是每需要存储一个数据才开辟一次空间,所以每个数据存储时都要单独的写入高速缓存区,再写入主存,这样就造成了,单链表CPU高速缓存效率低,且CPU流水线会经常被打断。

时间上的比较(Time)

a. 访问随机元素的时间复杂度:
顺序表访问随机元素的时间复杂度是O(1),而单链表访问随机元素的平均时间复杂度是O(n)。
b. 随机位置插入、删除元素的时间复杂度:
顺序表在插入随机位置插入、删除元素的平均时间复杂度是O(n),
单链表在插入随机位置插入、删除元素的时间复杂度是O(1)。

应用场景:

在查询操作使用的比较频繁时,使用顺序表会好一些;
在插入、删除操作使用的比较频繁时,使用单链表会好一些。

二、如何从头到尾打印单链表(使用递归的思想,我们便很容易有思路)

void PrintHeadtoTail(ListNode* plist)
{
    if(plist == NULL)
    {
        printf("NULL");
        return;
    }
    PrintHeadtoTail(plist->next);
    printf("<-%d",plist->data);
}

三、删除一个无头单链表的非尾节点

这里写图片描述

实现思路:无头单链表我们是无法找到非尾节点的前一个节点的,只能通过把目标节点的下一个节点的信息复制给自己,然后删除下个节点,达到删除自己的目的(即自己所报的信息不存在了)。

void EraseNonTail(ListNode* pos)//删除无头链表指定非尾节点
{
    if ((pos==NULL)&&(pos->next==NULL))
    {
        return;
    } 
    else
    {
        ListNode* sur = pos->next;
        pos->next = sur->next;
        pos->data = sur->data;
        free(sur);
        sur = NULL;
    }
}

四、非头节点前插入节点

这里写图片描述

    void AddNoHeadTail(ListNode*pos, DataType x)//非头节点前插入节点
{
    ListNode* tmp = NULL;
    assert(pos);

    tmp = BuyList(x);
    tmp->next = pos->next;
    pos->next = tmp;
    tmp->data = pos->data;
    pos->data = x;  
}

四、单链表实现约瑟夫环

这里写图片描述

ListNode* JosephRing(ListNode*list, DataType k)//单链表实现约瑟夫环
{
    ListNode* cur = list;
    ListNode* pos = list;
    if (list==NULL)
    {
        return NULL;
    }
    //首尾相连成环
    while(pos->next)
    {
        pos = pos->next;
    }
    pos->next = list;
    while (cur->next!=cur)//终止条件为只剩一个节点
    {
        DataType n = k;
        while (n--)
        {
            pos = cur;
            cur = cur->next;
        }
        pos->next = cur->next;
        free(cur);
        cur = pos->next;
    }
    return cur;
}

5.逆置、翻转单链表

这里写图片描述

ListNode* Reverse(ListNode* list)//逆置反转单链表
{
    ListNode* newhead = NULL;
    ListNode* pos = list;
    ListNode* cur = pos;
    while (cur)
    {
        pos = cur;
        cur = cur->next;
        pos->next = newhead;
        newhead = pos;
    }
    return newhead;
}

6.排序单链表

这里写图片描述

void Bubblesort(ListNode* list) //排序单链表
{
    ListNode* tail = NULL;
    ListNode* next = NULL;
    ListNode* pos = NULL;

    if ((list==NULL)||(list->next == NULL))
    {
        return;
    }

    while (list->next != tail)
    {
        pos = list;
        next = pos->next;

        while (tail != next)
        {
            if (pos->data > next->data)
            {
                DataType tmp = pos->data;
                pos->data = next->data;
                next->data = tmp;
            }
            pos = pos->next;
            next = next->next;          
        }
        tail = pos;
    }
}

7.合并有序单链表,合并后依然有序

这里写图片描述

ListNode* Conbine(ListNode* list1, ListNode* list2)
{
    ListNode* head = NULL;
    ListNode* tail = NULL;

    if (list1 == NULL)
    {
        return list2;
    }   
    if (list2 == NULL)
    {
        return list1;
    }
    //先找出两个链表中的一个小的节点,把他摘下来做新链表的头结点
    if(list1->data > list2->data)
    {
        head = list2;
        list2 = list2->next;
    }
    else
    {
        head = list1;
        list1 = list1->next;
    }
    tail = head;

    while (list1 && list2)
    {
        if (list1->data > list2->data)
        {
            tail->next = list2;
            list2 = list2->next;    
        } 
        else
        {
            tail->next = list1;
            list1 = list1->next;
        }
        tail = tail->next;
    }
    if (list1 == NULL)
    {
        tail->next = list2;
    } 
    else 
    {
        tail->next = list1;
    }
    return head;    
}

8.查找单链中间节点(只遍历一次链表)

这里写图片描述

ListNode* FindMidTail(ListNode* list)
{
    ListNode* fast = list;
    ListNode* slow = NULL;

    while ((fast!=NULL)&&(fast->next != NULL))
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;    
}

9.查找单链表中的倒数第k个节点

这里写图片描述

ListNode* FindTail_k_Node(ListNode* list, int k)
{
    ListNode* fast = list;
    ListNode* slow = list;

    while (--k)
    {
        if(fast == NULL)//说明k的值大于链长
        {
            return NULL;
        }
        fast = fast->next;
    }

    while (fast->next)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

10.判断一个链表是否带环,若带环求环入口点,环长

这里写图片描述

//判断是否带环,带环返回入口点,不带环返回空
ListNode* JudgeLoop(ListNode* list)
{
    ListNode* fast = list;
    ListNode* slow = list;

    while (fast && fast->next)
    {
        slow = slow->next;          
        fast = fast->next->next;
        if (fast == slow)
        {
            //printf("该单链表有环\n");
            return fast;
        }
    }
    //printf("该链表无环\n");
    return NULL;
}

ListNode* GetCyclen(ListNode* list)//若带环求环的节点环长
{
    ListNode* meetnode = JudgeLoop(list); 
    ListNode* fast = NULL;
    ListNode* slow = NULL;
    ListNode* loopnode = NULL;//环内相遇点   
    ListNode* listnode = NULL;
    int count = 1;//声明计数器,记录环长

    if (meetnode == NULL)
    {
        return NULL;
    }

    fast = meetnode->next->next;
    slow = meetnode->next;
    listnode = list;//起始点
    loopnode = meetnode;//环内相遇点 

    while (fast != slow)
    {
        fast = fast->next->next;
        slow = slow->next;
        count++;
    }
    printf("环长为:%d\n", count);
    while (listnode != loopnode)
    {
        listnode = listnode->next;
        loopnode = loopnode->next;
    }
    printf("环的入口点为:%d\n", loopnode->data);
    return loopnode; //环的入口点
}

11.判断两条链表是否相交,若相交返回相交点(假设不带环)

这里写图片描述

//判断两个链表是否相交,假设不带环(链表长度相减法)
ListNode* CheckIsMeet(ListNode* plist1, ListNode* plist2)
{
    int len1 = 0;
    int len2 = 0;
    int len = 0;
    ListNode* pos1 = plist1;
    ListNode* pos2 = plist2;

    if ((plist1 == NULL)||(plist2 == NULL))
    {
        return NULL;
    }

    while (pos1->next)
    {
        pos1 = pos1->next;
        len1++;
    }
    while (pos2->next)
    {
        pos2 = pos2->next;
        len2++;
    }

    len =len1 - len2;

    pos1 = plist1;
    pos2 = plist2;
    if(len>0)
    {   
        while (len--)
        {
            pos1 = pos1->next;
        }
    }
    else
    {
        len = abs(len); //对len求绝对值
        while (len--)
        {
            pos2 = pos2->next;
        }
    }
    while (pos1)
    {
        if (pos1 == pos2)
        {
            return pos1;
        }
        pos1 = pos1->next;
        pos2 = pos2->next;
    }
    return NULL;
}

12.链表相交问题升级版(可能带环)

这里写图片描述

//判断链表是否相交(可能带环)
ListNode* CheckMeet_All(ListNode* list1, ListNode* list2)
{
    ListNode *cur1 = JudgeLoop(list1);
    ListNode *cur2 = JudgeLoop(list2);

    ListNode *ent1 = NULL;
    //1.两个链表都不带环
    if ((cur1 == NULL) && (cur2 == NULL))
    {
        return CheckIsMeet(list1, list2); 
    }
    //2.一个带环,一个不带环(不可能相交)
    else if (((cur1==NULL)&&(cur2!=NULL))||((cur1!=NULL)&&(cur2==NULL)))
    {
        return NULL;
    }
    //3.两个链表都带环,分两种情况
    else
    {   
        //1.入口点相同则尾交,去掉环转换成不带环链表相交问题
        if (cur1 == cur2)
        {       
            cur1->next = NULL;
            return CheckIsMeet(list1,list2);
        }
        //2.同环两个入口点
        //让一个入口点遍历一遍看是否有另一个入口点
        else 
        {
            ent1 = cur1->next;
            while(ent1 != cur1)
            {
                if (ent1 == cur2)
                {
                    return ent1;
                }
            ent1 = ent1->next;
            }           
            return NULL;
        }       
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fly_bit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值