C语言的单链表操作

本文详细介绍了如何使用C语言来设计和操作单链表,包括创建不储值的头结点、根据索引获取和设置值、头插法和尾插法插入结点、按索引插入和删除结点以及释放链表内存的方法。重点强调了不储值头结点在简化逻辑和处理边界条件中的作用。
摘要由CSDN通过智能技术生成

        借助力扣上设计链表的这道题,我为大家详细解析下C语言的单链表应该怎么写

链接:707. 设计链表 - 力扣(Leetcode)

1.设置链表结点

           结点的内容分为两部分,一部分是数据域,一部分是指针域,数据域存储了你想存储的数据,而指针域指向下一个结点,从结点的写法我们就可以看出来,链表它在逻辑上是连续的,但是他不像数组在空间上是连续的,因为我们后面创建结点时候使用的动态内存开辟函数,malloc或是calloc都是系统随机分配一块空间的,如下图

         因为题目指定了结点的内容分为val和next,代码如下

typedef struct MylinkedList{
    int val;
    struct MyLinkedList* next;
} MyLinkedList;

我在struct的后面补全了 MylinkedList,更利于理解,这样子MyLinkList就可以直接代表struct MyLinkedList,简化后面代码的书写

2.创建链表

        创建链表的头结点,单链表的头结点分为储值头结点和不储值的头结点,我在这里使用是不储值的头结点,这与我们后面要实现的操作有关系,使用不储值的头结点方便我们只用一种逻辑就可以处理所有的情况,而不用对于特定情况再拿出来单独写代码分析,代码如下

MyLinkedList* myLinkedListCreate() {
    MyLinkedList* ret = (MyLinkedList*)malloc(sizeof(struct MyLinkedList));
    ret -> next = NULL;
    return ret;
}

        先为头结点申请一块空间,数据域不用赋值,因为我们不用头结点存储,它只是开始标志,然后将指针域置为NULL,最后返回,

3.根据索引求值

     

        画图解释下我写的代码怎么看索引

         所以我们先设置一个指针指向结点0,也就是obj -> next,然后通过遍历链表找到目标索引结点,并返回其值,如果找不到返回-1,代码如下

int myLinkedListGet(MyLinkedList* obj, int index) {
    int i = 0;
    MyLinkedList* cur = obj -> next;
    for(i = 0; cur != NULL; i++)
    {
        if(i == index)
        {
        return cur -> val;
        }
        else
        {
            cur = cur -> next;
        }
    }
    return -1;
}

4.头插法插入结点

     

         这乍一看就像是头结点的next指向新节点,然后新结点的next指向结点0,但是我们可不能这么写,如果一开始就将头结点的next指向新节点,那么我们再也找不到结点0了,所以我们要先将新节点的next指向结点0,再将头结点的next指向新节点,代码如下

void myLinkedListAddAtHead(MyLinkedList* obj, int val) {
    MyLinkedList* newNode = (MyLinkedList*)malloc(sizeof(MyLinkedList));
    newNode -> val = val;
    newNode -> next = obj -> next;
    obj -> next = newNode;
}

5.尾插法插入结点

        逻辑上与头插法一模一样,我们只需要找到尾部再重复一下头插法的操作就好了,代码如下

void myLinkedListAddAtTail(MyLinkedList* obj, int val) {
    MyLinkedList* ntail = obj;
    while(ntail -> next != NULL)
    {
        ntail = ntail -> next;
    }
    MyLinkedList* newNode = (MyLinkedList*)malloc(sizeof(MyLinkedList));
    newNode -> val = val;
    newNode -> next = ntail -> next;
    ntail -> next = newNode;
}

在这里我想讨论下为什么要使用不赋值的头结点,我们通过找到前一个结点来进行尾插,如果链表中只有一个结点的话,而且又使用赋值头结点的写法的话,第一个节点就是头结点,它是没有前一个节点的,就要单独拿出来分类讨论,但是使用不赋值头结点的写法的话,第一个节点是有前一个结点的,前一个结点也就是头结点,这样我们用同一种逻辑就可以处理所有的情况

6.根据索引插入结点

        根据索引插入结点,我们只要找到目标索引的前一个结点就可以利用同样的逻辑插入了,在这里我们也可以看到使用不赋值头结点的好处,代码如下

void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
    MyLinkedList* cur = obj;
    int i = 0;
    for(i = 0; cur != NULL; i++)
    {
        if(index == i)
        {
            MyLinkedList* newNode = (MyLinkedList*)malloc(sizeof(MyLinkedList));
            newNode -> val = val;
            newNode -> next = cur -> next;
            cur -> next = newNode;
        }
        else
        {
            cur = cur -> next;
        }
    }
}

在这里我们的cur指针指向头结点,然后i从0开始遍历,当i等于index时候,cur指向了目标索引结点的前一个结点

7.根据索引删除结点

        删除结点的操作之前还没出现过,这里画图解释一下,假设删除结点2

        简而言之就是将结点1的next指向结点3,但我们需要保存结点2的位置,用于后面free结点2的空间,而且通过观察我们可以发现这根插入操作逻辑相近,关键也在于找到目标索引的前一个结点,再次看到了不赋值头结点的好处,代码如下

void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
    MyLinkedList* cur = obj;
    int i = 0;
    for(i = 0; cur != NULL; i++)
    {
        if(index == i)
        {
            if(cur -> next != NULL)
            {
            MyLinkedList* targetNode = cur -> next;
            cur -> next = targetNode -> next;
            free(targetNode);
            }
        }
        else
        {
            cur = cur -> next;
        }
    }
}

        在这中间有个细节需要注意下,因为我们的cur指向的前一个结点,我们需要判断目标索引结点存不存在,你可能想问怎么之前插入时候不判断,因为之前是插入操作,不存在就插入,也就不用判断,而现在是删除操作,不存在就不用删除

8.释放列表

        将包括头结点的所有结点一个个释放,代码如下

void myLinkedListFree(MyLinkedList* obj) {
    while(obj)
    {
        MyLinkedList* cur = obj;
        obj = obj -> next;
        free(cur);
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值