C语言实现单链表面试题_基础篇

1.比较顺序表和链表的优缺点,说说它们分别在什么场景下使用?

顺序表中的数据存储在连续的存储空间内,所以查找的效率更高,但是插入和删除需要移动大量的数据,效率较低,适用于存储经常查找但很少插入和删除的数据;
链表因为存储在不连续的存储空间内,所以查找起来效率较低,但是插入或删除数据不需要移动其他数据,故插入和删除操作效率较高。适用于存储经常插入和删除而很少查找的数据。

2.从尾到头打印单链表

使用递归很容易就能实现逆序打印,代码如下:

void tail_to_head(ListNode* pList)
{
    if(pList==NULL)
    {
        printf("%s",pList);
        return;
    }
    else
        tail_to_head(pList->next );
    printf("->%d",pList->data );

}

用此函数虽然能够实现从尾到头打印单链表的目的,但是打印出来的结果最后面没有换行符,如果我们在从尾到头打印单链表的同时还需要打印其他东西,就会导致屏幕输出的结果相当混乱,最后我想出来的解决办法再写一个函数,在此函数中调用上面的那个函数,然后输出换行符。代码如下:

void tail_to_headPrintList(ListNode* pList)
{
    tail_to_head(pList);
    printf("\n");
}

3.删除一个无头单链表的非尾节点

此函数与Erase函数(删除指定节点)的不同之处是此函数没有给出单链表的起始位置指针,而只给出了需要删除的节点的指针pos,我们知道,要删除pos,不仅要释放pos所指向的空间,而且要让pos的前一个节点的next指针指向pos的下一个节点,但是此函数只给出了pos指针,我们没有办法找到pos的前一个节点,就无法直接删除pos,但是通过pos,我们可以删除pos的后一个节点,我们只需将pos后一个节点的内容挪动到pos指向的节点里,再删除pos的下一个节点,就相当于删除了pos。由于此函数在操作过程中要删除pos的下一个节点,所以pos不能为尾节点。
代码如下:

void Non_tail_Erase(ListNode* pos)
{
    ListNode* ptmp;
    assert(pos&&(pos->next ));
    pos->data =pos->next->data ;
    ptmp=pos->next;
    pos->next =pos->next ->next ;
    free(ptmp);
}

4.在无头单链表的一个节点前插入一个节点

和第三题一样,我们无法直接在pos的前面插入一个节点,但是我们可以在pos的后面插入一个节点,然后把pos的数据挪过去,然后在pos原来的位置上写入新的数据就好了。代码如下:

void Insert2(ListNode* pos, DataType x)
{
    ListNode* p=malloc(sizeof(ListNode));
    p->data =pos->data ;
    p->next =pos->next ;
    pos->data =x;
    pos->next =p;
}

5.单链表实现约瑟夫环

约瑟夫环是一个数学的应用问题:已知n个人(以编号1,2,3…n分别表示)围在一张圆桌周围,以编号为k的人开始报数数到m的那个人出列,他的下一个人有从1开始报数,数到m的人又出列,直至圆桌周围的人全部处理完。我们让单链表最后一个节点指向第一个节点,就可以模拟实现约瑟夫环问题了。代码如下:

ListNode* JosephCircle(ListNode* pList,int k,int m)
{
    int i;
    assert(pList);
    for(i=1;i<k;i++)
        pList=pList->next ;
    //使pList向后挪动k-1个节点,指向第k个节点
    while(1)
    {
        ListNode* ptmp;
        if(pList==pList->next )
            return pList;//只有一个节点时,返回这个节点的指针
        for(i=1;i<m;i++)
            pList=pList->next ;
        //使pList向后挪动m-1个节点,指向报数为m的节点
        ptmp=pList;//使ptmp指向报数为m的节点
        while(pList->next !=ptmp)
            pList=pList->next ;//循环一圈,使pList指向ptmp的前一个节点
        pList->next =ptmp->next ;
        //使报数为m的前一个节点指向m的后一个节点
        free(ptmp);//删除报数为m的节点
        pList=pList->next ;
        //使pList指向m的下一个节点,作为下一轮循环的第一个节点
    }
}

6.逆置/反转单链表

若单链表为空或只有一个节点,直接返回,若单链表含有多个节点:
1、将第一个节点的next指针指向NULL
2、将以后每个节点的next指针指向前一个节点
代码如下:

void ReverseList(ListNode** ppList)
{
    ListNode* pnext;
    ListNode* ptmp;
    assert(ppList);
    if((*ppList==NULL)||((*ppList)->next ==NULL))
        return;//链表为空或只有一个节点,直接返回
    pnext=(*ppList)->next ;
    //*ppList指向第一个节点,pnext指向第二个节点
    (*ppList)->next =NULL;//使第一个节点的next指向NULL
    while(pnext)//使以后每个节点的next指向前一个节点
    {
        ptmp=pnext->next;//保存第三个节点位置
        pnext->next =*ppList;//使第二个节点next指向第一个节点
        *ppList=pnext;//*ppList挪动到第二个节点
        pnext=ptmp;//pnext挪动到第三个节点
    }
}

7.单链表排序(冒泡排序&快速排序)

在这里我只写了从小到大的排序,从大到小方法是一样的。
冒泡排序从第一个节点开始处理,比较第一个和第二个的值,如果第一个节点大于第二个节点,交换他们的值,然后处理第二个节点和第三个节点,知道处理完倒数第二个节点和最后一个节点时,所有数据中最大的值就到了最后一个节点,然后开始进行第二轮循环,由于最后一个节点已经是最大的值了,所以不用参加第二轮循环。
代码如下:

void BubbleSortList(ListNode* pList)
{
    ListNode* p1=pList;
    ListNode* p2=NULL;
    if(NULL==pList)
        return;
    while(p1->next !=p2)
    {
        while(p1->next !=p2)
        {
            if((p1->data )>(p1->next ->data ))
            {
                DataType tmp=p1->data ;
                p1->data =p1->next ->data ;
                p1->next ->data =tmp;
                //如果p1节点的值大于下一个节点的值,交换它们的位置
            }
            p1=p1->next ;//p1指向下一个节点
        }/*循环结束时,最大的一个数被移动到了此次循环的
        最后一个节点,p1指向p2前一个节点*/
        p2=p1;//p2向前挪动一个节点
        p1=pList;//p1重新指向第一个节点
    }
}

快速排序是选取序列中的一个数作为基准数,把比它小的数都放在它左边,比它大的数都放在它右边,然后再对这两部分依次进行快速排序,直至每部分只有一个元素。
代码如下:

void QuickSortList(ListNode* pList)
{
    if((NULL==pList)||(NULL==pList->next ))
        return;//如果链表为空或只有一个节点,直接返回
    else
    {
        ListNode* pkey=pList;//将第一个节点的值设为基准数
        ListNode* p=pList->next;
        while(p)//从第二个节点开始遍历
        {
            if(p->data<pkey->data)
            {//如果p的值小于关键字,将它放在基准数的左边
                DataType tmp=pkey->data;
                pkey->data =p->data;//p的值放到pkey
                p->data =pkey->next->data;//pkey后一个节点的值放到p
                pkey->next->data =tmp;//pkey的值往后挪动一格
                pkey=pkey->next;//pkey指向跟随pkey的值往后移动一格,始终指向关键字
            }
            p=p->next;
        }
        //循环结束后pkey左边的值都小于基准数,右边的值都大于基准数
        p=pkey->next;
        pkey->next=NULL;//通过将pkey的next指针置为空将链表拆为两部分
        QuickSortList(pList);
        QuickSortList(p);//对两部分分别进行快速排序
        pkey->next=p;//重新将链表拼在一起
    }
}

8.合并两个有序链表,合并后依然有序

ListNode* MergeList(ListNode* p1,ListNode* p2)
{
    ListNode* pList;
    if(NULL==p1)//p1为空返回p2
        return p2;
    if(NULL==p2)//p2为空返回p1
        return p1;
    if(p1->data<p2->data)
    {
        pList=BuyNode(p1->data);
        p1=p1->next;
    }
    else
    {
        pList=BuyNode(p2->data);
        p2=p2->next;
    }
    while(p1&&p2)
    {
        if(p1->data<p2->data)
        {
            PushBack(&pList,p1->data);
            p1=p1->next;
        }
        else
        {
            PushBack(&pList,p2->data);
            p2=p2->next;
        }
    }
    //出此循环后,p1和p2至少有一个指向空
    while(p1)
    {
        PushBack(&pList,p1->data);
        p1=p1->next;
    }
    while(p2)
    {
        PushBack(&pList,p2->data);
        p2=p2->next;
    }
    return pList;
}

9.查找单链表的中间节点,要求只能遍历一次链表

此题可用快慢指针解决,即创建两个指针一起玩后走,一个每次移动一个节点,一个每次移动两个节点,这样快指针到达链表尾时,慢指针刚好指向链表的中间节点。
代码如下:

ListNode* FindMidList(ListNode* pList)
{
    ListNode* pfast=pList;
    if(NULL==pList)
        return NULL;//如果单链表为空,返回NULL
    while(pfast&&pfast->next)
    {
        pList=pList->next;
        pfast=pfast->next->next;
    }
    return pList;
}

10.查找单链表的倒数第k个节点,要求只能遍历一次链表

创建两个指针,一个先往后移动k个节点,然后一起往后移动,先走的到达NULL时,后面那个刚好指向倒数第k个。
代码如下:

ListNode* InverseList(ListNode* pList,int k)
{
    ListNode* ptail=pList;
    while(k--)
    {
        ptail=ptail->next;
    }
    while(ptail)
    {
        pList=pList->next;
        ptail=ptail->next;
    }
    return pList;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值