面试官:给你十分钟,写出可以增删查改任意位置插入删除的链表!

我有一个师兄,前几年去面试,笔试过了,到了面试环节,面试官提出了一个让他觉得面试官在为难他的一个问题:给你十分钟,写出可以增删查改任意位置插入删除的链表!
当时他一下子感觉这是不可能的,因为众所周知——链表的接口有初始化、打印、头插、头删、尾插、尾删、中间插入、中间删除、查找等这么多接口函数,看这些名字就知道把这些写完需要不少时间。
师兄说,他最快也要25分钟(他当时没有想到用双向循环带头链表),结果可想而知(挂了(;´д`)ゞ),后来回去他与老师探讨这一题,老师笑而不语,写下了:双向循环带头链表,师兄才恍然大悟!!!!
下面我来分享这道题的解法:
首先先讲解一下双向循环带头链表的各种接口,会在最后写下这道题的解法,如果不想看这些接口,可以直接划到最后看这道题的解法。
——————————————————————————————


1.链表的分类:

  1. 单向、双向
  2. 带头、不带头
  3. 循环、非循环
    图片说明

今天我们就来讲一讲链表中看似结构复杂的==双向循环带头链表==:
图片说明

2.双向链表的初始化:

方法一:传双指针

void ListNodeInit(ListNode** pphead)
{
    *pphead = ListNodeBuy(0);
    //ListNodeBuy(0)是建立一个存储0的地址的函数,后面会写上这个函数
    (* pphead)->next = *pphead;
    (*pphead)->prev = *pphead;
}

方法二:==返回值==法

ListNode* ListNodeInit()
{
    ListNode* phead = ListNodeBuy(0);
    phead->next = phead;
    phead->prev = phead;
    return phead;
}

再加上主函数上写:

ListNode* phead=ListNodeInit();

即可完成双向链表的初始化。

PS:双向循环带头链表为空时,即是只有头指针时,head->next和head->prev均指向自己(head)

3.双向链表的打印

(1)运行代码:

void ListPrint(ListNode* phead)
{
    assert(phead);
    ListNode* cur = phead->next;
    while (cur != phead)
    {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
}

这里要记得双向循环带头链表打印时的终止条件是:当cur遍历回到了phead!

(2)运行结果:

图片说明

4.双向链表的尾插:

(1)运行代码:

由于插入时要创建一个变量newnode,需向内存申请空间存储变量,且变量的值为插入的值,又因为每次插入都需要这一步,故将这一步写成一个函数,便于使用该函数为:

ListNode* ListNodeBuy(LTDataType x)
{
    ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
    newnode->next = NULL;
    newnode->prev = NULL;
    newnode->data=x;
    return newnode;
}

尾插函数:

void ListNodePushBack(ListNode* phead, LTDataType x)
{
    assert(phead);
    ListNode* tail = phead->prev;
    ListNode* newnode = ListNodeBuy(x);
    tail->next = newnode;
    newnode->prev = tail;
    newnode->next = phead;
    phead->prev = newnode;
}

(2)运行结果:

图片说明

5.双向链表的尾删

(1)运行代码:

void ListNodePopBack(ListNode* phead)
{
    assert(phead);
    //这是调用断言函数,该表达式的意思为判断phead是否为NULL
    assert(phead->next != phead);
    //这是判断该链表是否为空链表,若为空链表,则会报错
    ListNode* tail = phead->prev;
    ListNode* tail_prev = tail->prev;
    tail_prev->next = phead;
    phead->prev = tail_prev;
    free(tail);
    tail = NULL;
}

assert(phead);//这是调用==断言函数==,该表达式的意思为判断phead是否为NULL
assert(phead->next != phead);//这是判断该链表是否为==空链表==,若为空链表,则会报错

(2)运行结果:

图片说明

5.双向链表的头插:

(1)运行代码:

void ListNodePushFront(ListNode* phead, LTDataType x)
{
    assert(phead);
    ListNode* first = phead->next;
    ListNode* newnode = ListNodeBuy(x);
    phead->next = newnode;
    newnode->next = first;
    newnode->prev = phead;
    first->prev = newnode;
}

注:头插是是将==newnode==插在==phead==和==first==(头结点后的第一个结点)
图片说明

(2)运行结果:

图片说明

7.双向链表的头删:

(1)运行代码:

void ListNodePopFront(ListNode* phead)
{
    assert(phead);
    //这是调用断言函数,该表达式的意思为判断phead是否为NULL
    assert(phead->next != phead);
    //这是判断该链表是否为空链表,若为空链表,则会报错
    ListNode* first = phead->next;
    ListNode* second = first->next;
    phead->next = second;
    second->prev = phead;
    free(first);
    first = NULL;
}

图片说明

(2)运行结果:

图片说明

8.双向链表的查找

(1)运行代码:

ListNode* ListNodeFind(ListNode* phead, LTDataType x)
{
    assert(phead);
    ListNode* cur = phead->next;
    while (cur != phead)
    {
        if (cur->data == x)
            return cur;
        cur = cur->next;
    }
    return NULL;
}

查找后返回的是结点的指针,可以用==pos->data=某个数==进而修改这个点的值!!!

(2)运行结果:

图片说明

9.双向链表的中间插入:

(1)运行代码:

插入在指定位置的前面

void ListNodeInsert(ListNode* pos, LTDataType x)
{
    assert(pos);
    ListNode* pos_prev = pos->prev;
    ListNode* newnode = ListNodeBuy(x);
    newnode->next = pos;
    pos->prev = newnode;
    pos_prev->next = newnode;
    newnode->prev = pos_prev;
}

(2)运行结果:

图片说明

10.双向链表的中间删除:

(1)运行代码:

void ListNodeErase(ListNode* pos)
{
    assert(pos);
    ListNode* pos_prev = pos->prev;
    ListNode* next = pos->next;
    free(pos);
    pos = NULL;
    pos_prev->next = next;
    next->prev = pos_prev;
}

(2)运行结果:

图片说明

11.双向链表的销毁:

每一次用完链表后销毁是很有必要的,如果不销毁,将会导致==内存的泄露==,短时间内可能没用什么,且很难被发现,但时间长了,必将导致==程序的崩溃==,最终将导致时间和金钱的浪费!!!
这里介绍两种销毁:
方法一:销毁全部,不保留头结点:

void ListNodeDestroy(ListNode* phead)
{
    assert(phead);
    ListNode* cur = phead;
    while (cur != phead)
    {
        ListNode* next = cur->next;
        free(cur);
        cur = NULL;
        cur = next;
    }
    free(phead);
    phead=NULL;
}

方法二:保留头结点:

void ListNodeDestroy(ListNode* phead)
{
    assert(phead);
    ListNode* cur = phead;
    while (cur != phead)
    {
        ListNode* next = cur->next;
        free(cur);
        cur = NULL;
        cur = next;
    }
}

具体选择哪一种,需要看==实际需要==!!!

12.完整代码:

(1)List.h文件:

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
    struct ListNode* prev;
    LTDataType  data;
    struct ListNode* next;
}ListNode;
ListNode* ListNodeBuy(LTDataType x);
void ListNodeInit(ListNode** pphead);
void ListPrint(ListNode* phead);
void ListNodePushBack(ListNode* phead, LTDataType x);
void ListNodePopBack(ListNode* phead);
void ListNodePushFront(ListNode* phead, LTDataType x);
void ListNodePopFront(ListNode* phead);
ListNode* ListNodeFind(ListNode* phead, LTDataType x);
void ListNodeInsert(ListNode* pos, LTDataType x);
void ListNodeErase(ListNode* pos);
void ListNodeDestroy(ListNode* phead);

(2)List.c文件:

#include"List.h"
#define _CRT_SECURE_NO_WARNINGS 1
ListNode* ListNodeBuy(LTDataType x)
{
    ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
    newnode->next = NULL;
    newnode->prev = NULL;
    newnode->data=x;
    return newnode;
}
void ListNodeInit(ListNode** pphead)
{
    *pphead = ListNodeBuy(0);
    (* pphead)->next = *pphead;
    (*pphead)->prev = *pphead;
}
ListNode* ListNodeInit()
{
    ListNode* phead = ListNodeBuy(0);
    phead->next = phead;
    phead->prev = phead;
    return phead;
}
void ListPrint(ListNode* phead)
{
    assert(phead);
    ListNode* cur = phead->next;
    while (cur != phead)
    {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
}
void ListNodePushBack(ListNode* phead, LTDataType x)
{
    assert(phead);
    ListNode* tail = phead->prev;
    ListNode* newnode = ListNodeBuy(x);
    tail->next = newnode;
    newnode->prev = tail;
    newnode->next = phead;
    phead->prev = newnode;
}
void ListNodePopBack(ListNode* phead)
{
    assert(phead);
    assert(phead->next != phead);
    ListNode* tail = phead->prev;
    ListNode* tail_prev = tail->prev;
    tail_prev->next = phead;
    phead->prev = tail_prev;
    free(tail);
    tail = NULL;
}
void ListNodePushFront(ListNode* phead, LTDataType x)
{
    assert(phead);
    ListNode* first = phead->next;
    ListNode* newnode = ListNodeBuy(x);
    phead->next = newnode;
    newnode->next = first;
    newnode->prev = phead;
    first->prev = newnode;
}
void ListNodePopFront(ListNode* phead)
{
    assert(phead);
    assert(phead->next != phead);
    ListNode* first = phead->next;
    ListNode* second = first->next;
    phead->next = second;
    second->prev = phead;
    free(first);
    first = NULL;
}
ListNode* ListNodeFind(ListNode* phead, LTDataType x)
{
    assert(phead);
    ListNode* cur = phead->next;
    while (cur != phead)
    {
        if (cur->data == x)
            return cur;
        cur = cur->next;
    }
    return NULL;
}
void ListNodeInsert(ListNode* pos, LTDataType x)
{
    assert(pos);
    ListNode* pos_prev = pos->prev;
    ListNode* newnode = ListNodeBuy(x);
    newnode->next = pos;
    pos->prev = newnode;
    pos_prev->next = newnode;
    newnode->prev = pos_prev;
}
void ListNodeErase(ListNode* pos)
{
    assert(pos);
    ListNode* pos_prev = pos->prev;
    ListNode* next = pos->next;
    free(pos);
    pos = NULL;
    pos_prev->next = next;
    next->prev = pos_prev;
}
void ListNodeDestroy(ListNode* phead)
{
    assert(phead);
    ListNode* cur = phead;
    while (cur != phead)
    {
        ListNode* next = cur->next;
        free(cur);
        cur = NULL;
        cur = next;
    }
}
## (3)test.c文件:

```c
#include"List.h"
#define _CRT_SECURE_NO_WARNINGS 1
int main(void)
{
    ListNode* phead = NULL;
    ListNodeInit(&phead);
    /*ListNodePushBack(phead, 1);
    ListNodePushBack(phead, 2);
    ListNodePushBack(phead, 3);
    ListNodePushBack(phead, 4);
    ListNodePushBack(phead, 5);
    ListNodePushBack(phead, 6);
    ListPrint(phead);
    ListNodePopBack(phead, 1);
    ListNodePopBack(phead, 2);
    ListNodePopBack(phead, 3);
    ListNodePopBack(phead, 4);
    ListNodePopBack(phead, 5);
    ListPrint(phead);*/
    ListNodePushFront(phead, 1);
    ListNodePushFront(phead, 2);
    ListNodePushFront(phead, 3);
    ListNodePushFront(phead, 4);
    ListNodePushFront(phead, 5);
    ListNodePushFront(phead, 6);
    ListPrint(phead);
    /*ListNodePopFront(phead, 1);
    ListNodePopFront(phead, 2);
    ListNodePopFront(phead, 3);
    ListNodePopFront(phead, 4);
    ListNodePopFront(phead, 5);
    ListPrint(phead);*/
    ListNode* pos = ListNodeFind(phead,3);
    pos->data = 300;
    ListPrint(phead);
    ListNodeInsert(pos, 1);
    ListPrint(phead);
    ListNodeErase(pos);
    ListPrint(phead);
    ListNodeDestroy(phead);
    return 0;
}

13.这道面试题的解法:

这道题的解法:
首先先写一个结构体,然后
void ListNodeInit(ListNode* pphead);初始化(写出函数)
void ListPrint(ListNode
phead);只写函数名,这个即可
void ListNodePushBack(ListNode* phead, LTDataType x);只写函数名,这个即可
void ListNodePopBack(ListNode* phead);只写函数名,这个即可
void ListNodePushFront(ListNode* phead, LTDataType x);只写函数名,这个即可
void ListNodePopFront(ListNode* phead);只写函数名,这个即可
以上这几个函数只写函数名即可,面试官会理解这些的函数的作用
ListNode* ListNodeFind(ListNode* phead, LTDataType x);写出函数
void ListNodeInsert(ListNode* pos, LTDataType x);写出函数
void ListNodeErase(ListNode* pos);写出函数
这三个函数是大头,要写出函数操作。
如果你最后还是不放心,可以把这些写成这样:
请在这里输入引用内容
void ListNodePushBack(ListNode* phead, LTDataType x)
{ ListNodeInsert(phead,x);}
void ListNodePopBack(ListNode* phead)
{ ListNodeErase(phead->prev); }
void ListNodePushFront(ListNode* phead, LTDataType x)
{ ListNodeInsert(phead->next,x);}
void ListNodePopFront(ListNode* phead)
{ ListNodeErase(phead->next);}
请在这里输入引用内容

最后如果有时间,记得写上销毁函数,可以反映你有一定的项目经验,知道要销毁链表,防止内存泄***r>祝愿大家都可以拿到自己心仪的offer,成为offer收割机!!!

#C/C++#
全部评论
感谢大佬,讲解的很详细
3 回复 分享
发布于 2022-04-27 15:33
用两个vector分别存值和next指向的节点所处的位置也可以在O(1)时间复杂度写出来
2 回复 分享
发布于 2022-05-23 10:29
666,支持大佬
2 回复 分享
发布于 2022-04-28 13:05
感谢楼主的分享,点赞
2 回复 分享
发布于 2022-04-27 15:02
看笑了,你想说什么
1 回复 分享
发布于 2022-06-02 23:42
可以参考lru
1 回复 分享
发布于 2022-04-29 19:44
感谢楼主的分享
点赞 回复 分享
发布于 2022-06-13 14:02

相关推荐

废物一个0offer:认真的吗二本本科找人工智能岗位
点赞 评论 收藏
分享
想按时下班的大菠萝在...:隔壁学校的,加油多投, 实在不好找可以下个学期开学找,把算法八股准备好,项目有空再换换
投了多少份简历才上岸
点赞 评论 收藏
分享
评论
72
313
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务