基于单链表经常见的面试题——基础篇

本文详细介绍了单链表的基本操作及一些高级应用,包括插入、删除、排序等,并提供了具体的实现代码。通过实际案例展示了如何解决诸如约瑟夫环等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点击进入查看如何实现链表以及链表的一些基本操作函数
基于单链表的面试题——进阶篇

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

首先我们时间上来进行分析:

(1)对于顺序表。不论是静态的还是动态的,他们都是连续的存储空间,在读取上时间效率比较快,可以通过地址之间的运算来进行访问,但是在插入和删除操作会出现比较麻烦的负载操作。
(2)对于链表,因为他是链式存储。在我们需要的时候才在堆上开辟空间,对于插入查找的方式比较便携。但是对于遍历的话需要多次的空间跳转。

其次,我们从空间申请方式来看:

(1)顺序表的空间开辟是在满的时候进行多空间的申请开辟。往往存在着2^n的开辟原则。在开辟次数比较多的时候,会出现比较大的空间浪费。

(2)链表的空间开辟是针对于单个节点的空间开辟访问,不存在多余的空间浪费。并且在碎片内存池的机制下。可以有效地利用空间。
通过上面的总结,我们可以知道顺序表和链表的不同主要是空间上和时间上的优势与缺陷的不同。可以分析出顺序表往往用于查找遍历操作比较频繁的情况下使用。链表则针对于数据删除修改的多操作性上的情况下使用。

2.从尾到头打印单链表

void reverse(ListNode* plist)
//要实现反过来输出链表,我们每访问到一个结点的时候
//先递归输出它后面的结点,再输出该结点自身
//问题:当链表非常长的时候,就会导致函数调用的层级很深
//从而有可能导致函数调用栈溢出。
{
    if (NULL == plist)
    {
        printf("NULL");
        return;
    }
    reverse(plist->next);
    printf("<-%d", plist->data);
}

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

void DelHeadlessTail(ListNode *pos)//删除无头链表的非尾结点
//由于链表的单向性,我们只能知道一个结点所指向的下一个结点
//我们可以把问题想象成是此结点与后一个结点交换
//只要把两个结点的数据域交换,把结点指向后一个结点所指向的结点
{
    assert(pos);
    ListNode *ppos = pos->next;
    pos->data = ppos->data;
    pos->next = ppos->next;
    free(ppos);
    ppos = NULL;
}

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

void InsertNotHeadNode(ListNode *pos,DataType num)
//4.在无头单链表的一个节点前插入一个节点
//实现原理
//A->pcur->B->C.....  
//A->pcur->new->B->C.....  
//A->new->pcur->B->C..... 
{
    assert(pos);
    ListNode *tmp = InitList(num);
    ListNode *ppos = pos;  //记住原来节点的位置
    tmp->next = pos->next;
    pos->next = tmp;
    tmp->data = pos->data;
    pos->data = num;
}

5.单链表实现约瑟夫环

问题描述:约瑟夫问题的一种描述是:编号为1,2,…,n的n个人按顺时针方向围坐一圈,每人持一个密码(正整数)。一开始任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止。

这里写图片描述

void YueSeFu(ListNode *plist, DataType num)//约瑟夫环问题,报数为num的人出局
{
    assert(plist);
    ListNode *cur = plist;
    ListNode *tmp = plist;

    if (plist->next == NULL)
    {
        return;
    }
    while (cur->next != NULL)
    {
        cur = cur->next;
    }
    cur->next = tmp;//环形链表
    cur = plist;
    while (cur->next != cur)//头不等于尾,也就是剩下1个人就终止循环
    {
        DataType n = num;
        while (--n)
        {
            tmp = cur;
            cur = cur->next;
        }
        tmp->next = cur->next;//删除cur也就是报数为num的人
        printf("出局的为%d\n", cur->data);
        free(cur);
        cur = tmp->next;//头往后走,也就是下一个人开始报数
    }
    printf("剩下的是%d\n",cur->data);
}

6.逆置/反转单链表

只要是修改头指针则必须传递头指针的地址,否则传递头指针值即可(即头指针本身)。这与普通变量类似,当需要修改普通变量的值,需传递其地址,否则传递普通变量的值即可(即这个变量的拷贝)。使用二级指针,很方便就修改了传入的结点一级指针的值。 如果用一级指针,则只能通过指针修改指针所指内容,却无法修改指针的值,也就是指针所指的内存块。

这里写图片描述

void ReverseList(ListNode **pplist) //逆序单链表  

{
    ListNode *cur = *pplist;//cur当前结点
    ListNode *prev = NULL;//前一个结点
    ListNode *prear = NULL;//后一个结点
    if (NULL == *pplist || NULL == (*pplist)->next)
        return;
    while (cur)
    {
        prear = cur->next;
        cur->next = prev;
        prev = cur;
        cur = prear;

    }
    *pplist = prev;
}

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

这里写图片描述

void SortList(ListNode *plist)//冒泡排序
{
    if ((plist == NULL) || (plist->next == NULL))
    {
        return;
    }
    int exchange = 0;
    ListNode *tail = NULL;
    while (tail != plist->next)
    {
        ListNode *cur = plist;
        ListNode *next = plist->next;
        while (next != tail)
        {
            if (cur->data > next->data)
            {
                DataType num = cur->data;
                cur->data = next->data;
                next->data = num;
                exchange = 1;
            }
            cur = cur->next;
            next = next->next;
        }
        if (exchange == 0)//冒泡优化
        {
            break;
        }
        tail = cur;
    }
}

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

这里写图片描述

ListNode *MergeList(ListNode *list, ListNode *plist)//合并有序链表并输出有序
{//归并法(尾插法)
    if (list == NULL)//如果链表1为空,则直接返回链表2的头指针
    {
        return list;
    }
    else if (plist == NULL)//如果链表2为空,则直接返回链表1的头指针
    {
        return list;
    }
    else
    {
        //找出新链表的头结点
        ListNode *head = NULL;
        if (list->data < plist->data)
        {
            head = list;
            list = list->next;
        }
        else
        {
            head = plist;
            plist = plist->next;
        }

        //尾插
        ListNode *tail = head;
        while (list && plist)
        {
            if (list->data < plist->data)
            {
                tail->next = list;
                list = list->next;
            }
            else
            {
                tail->next = plist;
                plist = plist->next;
            }
            tail = tail->next;
        }
        if (list)//循环结束,一个链表为空,头指针直接指向非空链表的开始结点
        {
            tail->next = list;
        }
        else
        {
            tail->next = plist;
        }
        return head;
    }
}

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

ListNode *MidNode(ListNode *list)//查找单链表的中间节点,要求只能遍历一次链表 
{
    //快慢指针问题,快指针每次走两步,慢指针走一步。
    //快指针停止时,慢指针刚好指向中间结点
    //链表个数为偶数时,输出中间两个结点的后一个,也就是slow
    //当面试官说把两个中间值都输出来,那就把Slow和slow,这里用大写区分
    //要输出中间值的第一个,那就更改循环条件为fast&&fast->next->next
    ListNode *slow = list;
    //ListNode *Slow = list;
    ListNode *fast = list;
    while (fast&&fast->next)//while (fast&&fast->next->next)
        //要注意边界问题
    {
        //Slow = slow;   //Slow比slow后一个位置
        slow = slow->next;
        fast = fast->next->next;
    }

    return slow;
}

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

//先让快指针走N步,然后快指针不为空的情况下,快慢指针依次向后挪动1位,始终差距N
ListNode* CountBackwards(ListNode *list, DataType num)
//查找单链表的倒数第k个节点,要求只能遍历一次链表
{
    ListNode *slow = list;
    ListNode *fast = list;
    while (--num)
    {
        fast = fast->next;
        if (fast == NULL)
        {
            return NULL;
        }
    }
    while (fast->next)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

提供全部代码,包括所有测试:

singlelinkedlist.h头文件

#ifndef __SINGLELINKEDLIST_H__
#include<stdio.h>
#include<windows.h>
#include<assert.h>
typedef int DataType;

typedef struct ListNode
{
    DataType data;
    struct ListNode *next;

}ListNode;

ListNode *InitList(DataType num);//初始化并赋值
void PushBack(ListNode **pplist, DataType num);//尾插
void PrintList(ListNode *plist);//输出
void PopBack(ListNode **pplist);//尾删
void PushFront(ListNode **pplist, DataType num);//头插
void PopFront(ListNode **pplist);//头删
ListNode* Find(ListNode *plist, DataType num);//查找
void Insert(ListNode** pplist, ListNode* pos, DataType x);//插入
void Erase(ListNode** pplist, ListNode* pos);//删除
void reverse(ListNode* pplist);//从尾到头打印链表
void DelHeadlessTail(ListNode* pos);//删除非尾结点
void InsertNotHeadNode(ListNode *pos, DataType num);//在一个节点前插入一个节点
void YueSeFu(ListNode *plist, DataType num);//约瑟夫环问题
void ReverseList(ListNode **pplist);//逆序单链表  
void SortList(ListNode *plist);//冒泡排序
ListNode *MergeList(ListNode *list, ListNode *plist);//(尾插法)合并有序链表并输出有序
ListNode *MidNode(ListNode *list);//查找单链表的中间节点,要求只能遍历一次链表 
ListNode* CountBackwards(ListNode *list, DataType num);//查找单链表的倒数第k个节点,要求只能遍历一次链表

#endif//__SINGLELINKEDLIST_H__

singlelinkedlist.c实现部分

#include "singlelinkedlist.h"


ListNode *InitList(DataType num)//定义一个新的结点
{
    ListNode *node = (ListNode*)malloc(sizeof(ListNode));
    node->data = num;
    node->next = NULL;
    return node;
}
void PushBack(ListNode **pplist, DataType num)//尾插
{

    if (*pplist == NULL)//空链表
    {
        *pplist = InitList(num);//定义一个结点
    }
    else if ((*pplist)->next == NULL)//只有一个结点
    {
        (*pplist)->next = InitList(num);
    }
    else//正常情况(多个结点)
    {
        ListNode *tail = *pplist;
        while (tail->next)
        {
            tail = tail->next;//依次指向下一个结点,找到为空的尾结点
        }
        tail->next = InitList(num);//找到以后直接添加一个结点
    }
}


void PrintList(ListNode *plist)//打印链表
{
    ListNode *tail = plist;
    while (tail)
    {
        printf("%d->", tail->data);
        tail = tail->next;
    }
    printf("NULL");
    printf("\n");
}


void PopBack(ListNode **pplist)//尾删
{
    if (*pplist == NULL)//空链表
    {
        return;
    }
    else if ((*pplist)->next == NULL)//只有一个结点,直接释放
    {
        free(*pplist);
        *pplist = NULL;
    }
    else
    {
        ListNode* tail = *pplist;
        ListNode* pos = tail;
        while (tail->next)//tail指向pos的后一个结点
        {
            pos = tail; 
            tail = tail->next;
        }
        free(tail);//释放最后一个结点就相当于删除了尾结点
        tail = NULL;
        pos->next = NULL;
    }
}

void PushFront(ListNode **pplist, DataType num)//头插
{
    if (*pplist == NULL)//空链表
    {
        *pplist = InitList(num);
    }
    else
    {
        ListNode *tmp = InitList(num);//开辟一个新的结点
        tmp->next = *pplist;//让它指向原先的开始结点
        *pplist = tmp;//pplist依然开始结点
    }
}

void PopFront(ListNode **pplist)//头删
{
    if (*pplist == NULL)//空链表
    {
        return;
    }
    else if ((*pplist)->next == NULL)//只有一个结点
    {
        *pplist = NULL;
    }
    else
    {
        ListNode *tmp = (*pplist)->next;//tmp指向原先头结点指向的下一个位置
        free(*pplist);
        *pplist = tmp;
    }

}

ListNode* Find(ListNode *plist, DataType num)//查找
{
    assert(plist);//断言其是否为空链表
    while (plist)
    {
        if (plist->data == num)
        {
            return plist;
        }
        plist = plist->next;
    }
    return NULL;
}


void Insert(ListNode** pplist, ListNode* pos, DataType num)//插入
{
    assert(*pplist&&pos);
    if (((*pplist)->next == NULL) || (pos == *pplist))
        //只有开始结点或者是要插入的正好在开始结点的前面
    {
        PushFront(pplist, num);
    }
    else
    {
        ListNode* tmp = NULL;
        ListNode* tail = *pplist;
        while (tail->next != pos)
        {
            tail = tail->next;
        }
        tmp = InitList(num);
        tail->next = tmp;
        tmp->next = pos;
    }
}

void Erase(ListNode** pplist, ListNode* pos)//删除
{
    assert(*pplist&&pos);
    if (((*pplist)->next == NULL) || (*pplist == pos))
    {
        PopFront(pplist);
    } 
    else
    {
        ListNode* tmp = *pplist;
        while (tmp->next != pos)
        {
            tmp = tmp->next;
        }
        tmp->next = pos->next;
        free(pos);
        pos = NULL;
    }
}

void reverse(ListNode* plist)
//要实现反过来输出链表,我们每访问到一个结点的时候
//先递归输出它后面的结点,再输出该结点自身
//问题:当链表非常长的时候,就会导致函数调用的层级很深
//从而有可能导致函数调用栈溢出。
{
    if (NULL == plist)
    {
        printf("NULL");
        return;
    }
    reverse(plist->next);
    printf("<-%d", plist->data);
}

void DelHeadlessTail(ListNode *pos)//删除无头链表的非尾结点
//由于链表的单向性,我们只能知道一个结点所指向的下一个结点
//我们可以把问题想象成是此结点与后一个结点交换
//只要把两个结点的数据域交换,把结点指向后一个结点所指向的结点
{
    assert(pos);
    ListNode *ppos = pos->next;
    pos->data = ppos->data;
    pos->next = ppos->next;
    free(ppos);
    ppos = NULL;
}

void InsertNotHeadNode(ListNode *pos,DataType num)
//4.在无头单链表的一个节点前插入一个节点
//实现原理
//A->pcur->B->C.....  
//A->pcur->new->B->C.....  
//A->new->pcur->B->C..... 
{
    assert(pos);
    ListNode *tmp = InitList(num);
    ListNode *ppos = pos;  //记住原来节点的位置
    tmp->next = pos->next;
    pos->next = tmp;
    tmp->data = pos->data;
    pos->data = num;
}

void YueSeFu(ListNode *plist, DataType num)//约瑟夫环问题,报数为num的人出局
{
    assert(plist);
    ListNode *cur = plist;
    ListNode *tmp = plist;

    if (plist->next == NULL)
    {
        return;
    }
    while (cur->next != NULL)
    {
        cur = cur->next;
    }
    cur->next = tmp;//环形链表
    cur = plist;
    while (cur->next != cur)//头不等于尾,也就是剩下1个人就终止循环
    {
        DataType n = num;
        while (--n)
        {
            tmp = cur;
            cur = cur->next;
        }
        tmp->next = cur->next;//删除cur也就是报数为num的人
        printf("出局的为%d\n", cur->data);
        free(cur);
        cur = tmp->next;//头往后走,也就是下一个人开始报数
    }
    printf("剩下的是%d\n",cur->data);
}


void ReverseList(ListNode **pplist) //逆序单链表  
{
    ListNode *cur = *pplist;//cur当前结点
    ListNode *prev = NULL;//前一个结点
    ListNode *pnext = NULL;//后一个结点
    if (NULL == *pplist || NULL == (*pplist)->next)
        return;
    while (cur)
    {
        pnext = cur->next;
        cur->next = prev;
        prev = cur;
        cur = pnext;

    }
    *pplist = prev;
}


void SortList(ListNode *plist)//冒泡排序
{
    if ((plist == NULL) || (plist->next == NULL))
    {
        return;
    }
    int exchange = 0;
    ListNode *tail = NULL;
    while (tail != plist->next)
    {
        ListNode *cur = plist;
        ListNode *next = plist->next;
        while (next != tail)
        {
            if (cur->data > next->data)
            {
                DataType num = cur->data;
                cur->data = next->data;
                next->data = num;
                exchange = 1;
            }
            cur = cur->next;
            next = next->next;
        }
        if (exchange == 0)//冒泡优化
        {
            break;
        }
        tail = cur;
    }
}


ListNode *MergeList(ListNode *list, ListNode *plist)//合并有序链表并输出有序
{//归并法(尾插法)
    if (list == NULL)//如果链表1为空,则直接返回链表2的头指针
    {
        return list;
    }
    else if (plist == NULL)//如果链表2为空,则直接返回链表1的头指针
    {
        return list;
    }
    else
    {
        //找出新链表的头结点
        ListNode *head = NULL;
        if (list->data < plist->data)
        {
            head = list;
            list = list->next;
        }
        else
        {
            head = plist;
            plist = plist->next;
        }

        //尾插
        ListNode *tail = head;
        while (list && plist)
        {
            if (list->data < plist->data)
            {
                tail->next = list;
                list = list->next;
            }
            else
            {
                tail->next = plist;
                plist = plist->next;
            }
            tail = tail->next;
        }
        if (list)//循环结束,一个链表为空,头指针直接指向非空链表的开始结点
        {
            tail->next = list;
        }
        else
        {
            tail->next = plist;
        }
        return head;
    }
}


ListNode *MidNode(ListNode *list)//查找单链表的中间节点,要求只能遍历一次链表 
{
    //链表个数为偶数时,输出中间两个结点的后一个,也就是slow
    //当面试官说把两个中间值都输出来,那就把Slow和slow,这里用大写区分
    //要输出中间值的第一个,那就更改循环条件为fast&&fast->next->next
    ListNode *slow = list;
    //ListNode *Slow = list;
    ListNode *fast = list;
    while (fast&&fast->next)//while (fast&&fast->next->next)
        //要注意边界问题
    {
        //Slow = slow;   //Slow比slow后一个位置
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}



ListNode* CountBackwards(ListNode *list, DataType num)
//查找单链表的倒数第k个节点,要求只能遍历一次链表
{
    ListNode *slow = list;
    ListNode *fast = list;
    while (--num)
    {
        fast = fast->next;
        if (fast == NULL)
        {
            return NULL;
        }
    }
    while (fast->next)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

test.c测试部分

#include "singlelinkedlist.h"



void test()
{
    ListNode *list = NULL;
    PushBack(&list, 1);
    PushBack(&list, 2);
    PushBack(&list, 3);
    PushBack(&list, 4);
    PrintList(list);
    PopBack(&list);
    PrintList(list);
    PopBack(&list);
    PrintList(list);
    PopBack(&list);
    PrintList(list);
    PopBack(&list);
    PrintList(list);
}



void test1()
{
    ListNode *list = NULL;
    PushFront(&list, 1);
    PushFront(&list, 2);
    PushFront(&list, 3);
    PushFront(&list, 4);
    PushFront(&list, 5);
    PrintList(list);
    PopFront(&list);
    PrintList(list);
    PopFront(&list);
    PrintList(list);
    PopFront(&list);
    PrintList(list);
    PopFront(&list);
    PrintList(list);
    PopFront(&list);
    PrintList(list);
}


void test2()
{
    ListNode *list = NULL;
    PushFront(&list, 1);
    PushFront(&list, 2);
    PushFront(&list, 4);
    PushFront(&list, 5);
    PushFront(&list, 6);
    PrintList(list);
    ListNode *ret = Find(list, 2);
    //测试使用
    /*if (ret != NULL)
    {
        printf("%p\n", ret);
    }
    else
    {
        printf("没有这个值!\n");
    }*/
    Insert(&list, ret, 3);
    PrintList(list);
    Erase(&list, ret);
    PrintList(list);
}


void test3()
{
    ListNode *list = NULL;
    PushFront(&list, 1);
    PushFront(&list, 2);
    PushFront(&list, 3);
    PushFront(&list, 4);
    PushFront(&list, 5);
    PrintList(list);
    reverse(list);//从尾到头打印链表
    printf("\n");
}


void test4()
{
    ListNode *list = NULL;
    PushFront(&list, 1);
    PushFront(&list, 2);
    PushFront(&list, 3);
    PushFront(&list, 4);
    PushFront(&list, 5);
    PrintList(list);
    ListNode *ret = Find(list, 2);
    DelHeadlessTail(ret);//删除非尾结点
    PrintList(list);


}


void test5()
{
    ListNode *list = NULL;
    PushFront(&list, 1);
    PushFront(&list, 2);
    PushFront(&list, 4);
    PushFront(&list, 5);
    PushFront(&list, 6);
    PrintList(list);
    ListNode *ret = Find(list, 2);
    InsertNotHeadNode(ret, 3);//在一个节点前插入一个节点
    PrintList(list);
    YueSeFu(list, 2);//约瑟夫环问题
}



void test6()
{
    ListNode *list = NULL;
    PushFront(&list, 1);
    PushFront(&list, 2);
    PushFront(&list, 3);
    PushFront(&list, 4);
    PushFront(&list, 5);
    PrintList(list);
    ReverseList(&list);//单链表的逆置
    PrintList(list);
}


void test7()
{
    ListNode *list = NULL;
    PushFront(&list, 3);
    PushFront(&list, 2);
    PushFront(&list, 5);
    PushFront(&list, 4);
    PushFront(&list, 8);
    PrintList(list);
    SortList(list);//冒泡排序
    PrintList(list);

}


void test8()
{
    ListNode *tmp = NULL;
    PushFront(&tmp, 5);
    PushFront(&tmp, 3);
    PushFront(&tmp, 1);
    PrintList(tmp);
    ListNode *num = NULL;
    PushFront(&num, 6);
    PushFront(&num, 4);
    PushFront(&num, 2);
    PrintList(num);
    ListNode *ret = MergeList(tmp, num);//尾插法
    PrintList(ret);

}


void test9()
{
    ListNode *list = NULL;
    //PushFront(&list, 3);
    //PushFront(&list, 2);
    //PushFront(&list, 5);
    //PushFront(&list, 4);
    //PushFront(&list, 8);
    //ListNode *ret = MidNode(list);//查找单链表的中间节点,要求只能遍历一次链表 
    //printf("%d\n",ret->data);//链表个数如果是奇数个,输出中间那个
    PushFront(&list, 3);
    PushFront(&list, 2);
    PushFront(&list, 4);
    PushFront(&list, 8);
    ListNode *Ret = MidNode(list);//查找单链表的中间节点,要求只能遍历一次链表 
    printf("%d\n", Ret->data);//链表个数如果是偶数个,输出中间两个结点的后一个

}



void test10()
{
    ListNode *list = NULL;
    PushFront(&list, 1);
    PushFront(&list, 2);
    PushFront(&list, 3);
    PushFront(&list, 4);
    PushFront(&list, 5);
    PrintList(list);
    ListNode *ret = CountBackwards(list, 2);
    printf("%d\n",ret->data);

}



int main()
{
    //test();
    //test1();
    //test2();
    //test3();
    //test4();
    //test5();
    //test6();
    //test7();
    //test8();
    //test9();
    test10();
    system("pause");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值