保姆级基于C语言实现单链表的增删查改(内附源码)

前言简介

链表是物理存储单元上非连续的、非顺序的存储结构,数据之间的逻辑顺序链接是通过链表的指针实现的,由一系列结点组成,结点可依据需要动态生成,也是一种数据结构。

相对于顺序表的优点是其物理存储单元上非连续,而且采用动态内存分配,能够有效的分配和利用内存资源,然后是节点删除和插入简单,不需要内存空间的重组,缺点是不能像顺序表那样进行索引访问,只能从头结点开始顺序查找。

链表结点定义及其初始化

链表的结点定义

链表是由一个个基本单元组合而成的一种链式结构,所以我们需要有这种基本单元,才能组成一条“锁链”,如图是一个单链表结点的基本结构,分别由数据域和指针域组成(我一开始写这个链表的初衷是为了存储和管理学生数据,所以数据名称为Stu)

其中NAME_MAX和SEX_MAX为自己定义的宏参数,方便进行数组存储数据个数的修改。

链表的初始化

我们先进行这样的定义,然后就可以进行链表基本功能的操作和实现了,需要一个最开始的“空壳”,然后再对他进行充实。

经过上面的操作后,我们就有了链表的基础,一个个的结点,接下来我们就可以依靠这些进行链表功能实现了

四大基本功能实现

类似于这种可以存储数据元素的结构,我们都需要有四大基本功能——增删查改,方便我们进行数据管理,接下来让我们一一进行实现:

增:

我们可以有多种方式增加链表中数据元素数量,头部增加,尾部插入,任意插入等,接下来我们进行一一实现

头插:

  上面是一段头插的代码,首先我们进行了新节点的创造,调用的Slbuy函数(我另外写的一个函数,用于新节点的生成与返回,具体实现如下),传的参数是一个二级指针,因为要对一开始我们设置的那个空指针进行操作,所以需要他的地址,又因为原本的那个变量就是一个指针,所以这里我们用二级指针。 

(插入这部分我会很详细的讲解,后面删除,查找,修改其实都差不多,我就不会讲的很详细)

头插是一个非常轻松加愉快的事情,获得新结点后我们先进行判断,如果传过来的指针变量是一个空指针,那么肯定就是有问题的,因为一个指针变量的地址不可能为空,对空进行操作肯定有问题,我们直接assert断言(使用assert需要包含对应头文件assert.h),然后进行判断,如果将二级指针解引用后,得到的一级指针为NULL,也就是啥也没有,一开始我们定义的那个,我们就直接将我们的新结点的地址给予它,然后直接return返回结束调用,反之如果解引用后不为空则代表原链表已经有数据(至少有一个结数据),则直接将新结点的Node的next指针指向其,然后将新节点设置为头节点,这样我们就完成了头插。

尾插:

上面是一段尾插的代码,还是老样子,先assert断言一下,然后使用Slbuy创建一个新的结点,并将其地址传给Node,依然进行判断,如果原链表啥都没有,也就是说这是第一个结点,我们就直接将这个新节点设置为头节点,反之则定义一个指针变量pcur进行链表遍历找尾,然后将原尾的next指向新的node,新的node就变成了尾,这样一个简单的尾插就实现了。

依照上面的思路,你可以试着自己完成一下链表的任意插。

删:

头删:

上面是一个简单头删的代码,首先断言大家应该都知道为什么了,就不多讲,不知道的小伙伴可以看上面增的部分,然后就是进行*pSl的判断,如果此时*pSl为空,则说明原链表是无数据的,则不能删除,直接判断后返回,如果过了这层判断则说明有数据,如上图所写,进行删除,不多赘述(*pSl)之所以用括号括起来是因为操作符的结合优先级问题,->的结合优先级大于*,所以我们需要用圆括号扩起来把*和pSl。

尾删:

上面是一个简单头删的代码,断言大家懂的都懂,然后就是老样子判空,接下来有点特殊,我们进行一个判断,就是判断只有一个结点的情况和有两个和两个以上的结点(为什么要这么判断捏?因为我就只想到这样写,如果有更好的写法,欢迎留在评论区,敬请赐教!)

上半段代码的思路,也就是只有一个结点的那段,我们就用一个指针保存那一个结点(它既是头也是尾),然后我们将那一个唯一的结点的地址更新到他的下一个结点,其实就是NULL,然后我们再free释放到原本的尾尾(头),也就是那唯一一个结点的内存,这样就完成了一个结点情况的简单尾删。

下半段代码其实也差不多,无非就是找尾和保存尾巴前一个结点,然后将尾的前一个结点的next指向尾的next(其实就是NULL),思路很简单,就不多赘叙了。

根据上面两个删除的思路,小伙伴可以试着完成一下链表的任意删除功能,我会在最后把我自己的思路的任意删贴上。

查:

上面是一个查找函数的实现,首先依然是判空,如果为空,则没有查找的必要,直接返回即可,如果有数据,则让用户输入要查找的信息,然后进行链表遍历查找,使用strcmp函数进行比对,(strcmp函数用于两个字符串的比较,如果两个字符串相同,则返回0,其他情况则返回非0),进行学生信息查找,若找到则告诉用户,找到了,并且打印该用户信息,然后返回,若是遍历了整个链表还没有找到对应的名字,则没有该人员信息,则告诉用户,无该人员。

上面为打印某个结点人员信息的函数,在查找函数中调用了这个函数。

这样我们整个查找函数则实现了。

改:

上面是一段“改”的代码,可以修改链表中对应的人员的信息

老生常谈了,进行断言,然后判空,接着我们定义一个name数组,然后对其进行名字的输入,查找对应人员,定义一个临时变量,进行链表遍历,直到查找到人或者遍历完没找到,若找到了,则直接输入新的信息即可,思路简单清晰,知识点都是上面用过的,复现一遍,大家可以自己试着实现。

源代码

最后附上源代码

void SljudgePush(Slnode** pSl)
{
    int judge = 0;
    int num = 0;
    int flag = 3;
end:
    printf("请选择头插,尾插还是任意插\n");
    printf("0,退出\n1,头插\n2,尾插\n3,任意插\n");
    scanf("%d", &judge);
    switch (judge)
    {
    case 0:
    {
        printf("退出成功\n");
        return;
    }
    case 1:
    {
        printf("需要头插几次?->\n");
        scanf("%d", &num);
        while (num)
        {
            SlheadPush(pSl);
            num--;
            printf("请继续输入\n");
        }
        break;
    }
    case 2:
    {
        printf("需要尾插几次?->\n");
        scanf("%d", &num);
        while (num)
        {
            SlbackPush(pSl);
            num--;
            printf("请继续输入\n");
        }
        break;
    }
    case 3:
    {
        printf("任意插\n");
        printf("请输入要插入在哪一个学生之前\n");
        char name[NAME_MAX];
        scanf("%s", name);
        SlarbPush(name, pSl);
        break;
    }
    default:
    {
        if (flag==3)
        {
            printf("不要皮了!!!(*_*)\n");
            flag--;
        }
        else if (flag == 2)
        {
            printf("你还皮!敲你脑壳\n");
            flag--;
        }
        else if (flag == 1)
        {
            printf("哎!你还真上瘾了是吧!\n");
            flag--;
        }
        else
        {
            printf("算了,你爱咋地咋地(摆烂)\n");;
            return;
        }
        goto end;
    }

    }

}

void SljudgePop(Slnode** pSl)
{
    int judge = 0;
    int num = 0;
    int flag = 3;
end:
    printf("请选择头删还,尾插还是任意删\n");
    printf("0,退出\n1,头删\n2,尾删\n3,任意删\n");
    scanf("%d", &judge);
    switch (judge)
    {
    case 0:
    {
        printf("退出成功\n");
        return;
    }
    case 1:
    {
        printf("需要头删几次?->\n");
        scanf("%d", &num);
        while (num)
        {
            SlPophead(pSl);
            num--;
        }
        break;
    }
    case 2:
    {
        printf("需要尾删几次?->\n");
        scanf("%d", &num);
        while (num)
        {
            SlPopback(pSl);
            num--;
        }
        break;
    }
    case 3:
    {
        printf("任意删\n");
        printf("请输入要删除的学生的名字\n");
        char name[NAME_MAX];
        scanf("%s", name);
        SlarbPop(name, pSl);
        break;
    }
    default:
    {
        if (flag == 3)
        {
            printf("不要皮了!!!(*_*)\n");
            flag--;
        }
        else if (flag == 2)
        {
            printf("你还皮!敲你脑壳\n");
            flag--;
        }
        else if (flag == 1)
        {
            printf("哎!你还真上瘾了是吧!\n");
            flag--;
        }
        else
        {
            printf("算了,你爱咋地咋地(摆烂)\n");;
            return;
        }
        goto end;
    }

    }
}

void SlheadarbPush(Slnode **pSl,char name[])//任意头插
{
    
    Slnode* Node = Slbuy();
    if (!strcmp((*pSl)->Stu->name, name))//第一个就是的话
    {
        Node->next = *pSl;
        *pSl = Node;
        return;
    }
    Slnode* Tem = *pSl;
    Slnode* Tem1 = NULL;
    while (strcmp(Tem->Stu->name, name))//两个或者两个以上,且不为第一个,此时Tem就是目标
    {
        Tem1 = Tem;
        Tem = Tem->next;
    }
    Node->next = Tem;
    Tem1->next = Node;
    printf("插入成功\n");
}


void SlheadPush(Slnode** pSl)//普通头插
{
    assert(pSl);
    Slnode* Node = Slbuy();
    if (*pSl == NULL)
    {
        *pSl = Node;
        return;
    }
    Node->next = *pSl;
    *pSl = Node;
    printf("插入成功\n");
}


void SlbackPush(Slnode** pSl)
{
    assert(pSl);
    Slnode* Node = Slbuy();//创建新节点
    if (*pSl == NULL)
    {
        *pSl = Node;
        return;
    }
    Slnode* pcur = *pSl;//创建一个临时指针变量并设置为传进来的指针解引用后的地址
    while (pcur->next!=NULL)//遍历整个链表,找尾
    {
        pcur = pcur->next;
    }

    pcur->next = Node;//找到尾后将尾的next指向新的node即可
    printf("插入成功\n");
    
}


void SlPophead(Slnode** pSl)//头删
{
    assert(pSl);//断言,因为pSl是不可能为空的
    if (*pSl == NULL)//如果pSl解引用后为空则说明原链表无数据,则无法删除
    {
        printf("当前无数据,无法删除\n");
        return;
    }
    Slnode* Tem = *pSl;//保存此时的头
    *pSl = (*pSl)->next;//将原头的next指向原头的下一个结点
    free(Tem);//释放掉原头的内存,避免内存泄露
    printf("删除成功!\n");
}


void SlPopback(Slnode** pSl)
{
    assert(pSl);
    if (*pSl == NULL)
    {
        printf("当前无数据,无法删除\n");
        return;
    }
    if ((*pSl)->next == NULL)//只有一个节点
    {
        Slnode* pre = *pSl;//用于保存尾结点的前一个结点
        *pSl = (*pSl)->next;
        free(pre);
        return;
    }
    Slnode* pre = NULL;//用于保存尾结点的前一个结点
    Slnode* mpSl = *pSl;//寻找尾结点
    while (mpSl->next)//循环结束后,mpSl则为尾结点,pre则为尾节点前一个
    {
        pre = mpSl;
        mpSl = mpSl->next;
    }
    pre->next = mpSl->next;
    free(mpSl);
    printf("删除成功!\n");
}


void SlarbPush(Slnode** pSl,char name[])
{
    if (*pSl == NULL || pSl == NULL)
    {
        SlheadPush(pSl);
        printf("插入成功!\n");
        return;
    }

    if (SlFindStu(name, *pSl))
    {    
        SlheadarbPush(pSl,name);
        printf("插入成功!\n");
        return;
    }
    printf("该学生不存在,无法插入!\n");

}


void SlarbPop(Slnode** pSl, char name[])
{
    if (*pSl == NULL || pSl == NULL)
    {
        printf("无法删除,无数据\n");
        return;
    }
    if (SlFindStu(name, *pSl))
    {
        Slnode* Tem = *pSl;
        Slnode* Tem1 = NULL;
        if ((*pSl)->next == NULL||!strcmp(Tem->Stu->name, name))//只有一个节点且找到,或者第一个节点
        {
            Tem1 = Tem->next;
            free(Tem);
            *pSl = Tem1;
            printf("删除成功!\n");
            return;
        }
        while (strcmp(Tem->Stu->name, name))//有两个或者两个以上节点,且第一个不是,此时Tem就是目标
        {
            Tem1 = Tem;
            Tem = Tem->next;
        }
        Tem1->next= Tem->next;
        free(Tem);
        Tem = NULL;
        printf("删除成功!\n");
        return;
    }
    printf("该学生不存在,无法删除!\n");
}


void SlrevStu(Slnode** pSl)
{
    assert(pSl);//断言判空
    if (*pSl == NULL)//判断链表中数据不为空
    {
        printf("当前无数据,无法更改\n");
        return;
    }
    printf("请输入要修改的人的人名\n");
    char name[NAME_MAX];
    scanf("%s", name);//还是一样,我们规定使用人名进行查找,也可以用别的,根据情况更换判断条件
    Slnode* Tem = *pSl;//定义一个指针变量,初始化其为*pSl的地址
    while (Tem)//使用它进行链表的遍历
    {
        if (!strcmp(name, (Tem)->Stu->name))//通过strcmp函数进行字符串的比对,找到对应的人
        {
            printf("请输入新的名字:\n");
            scanf("%s", Tem->Stu->name);
            printf("请输入新的性别:\n");
            scanf("%s", Tem->Stu->sex);
            printf("请输入新的学号:\n");
            scanf("%d", &Tem->Stu->num);
            printf("修改完成\n");
            return;
        }
        Tem = Tem->next;
    }
    printf("未找到该人员!\n");
}

void SlAllPrint(Slnode* pSl)
{
    if (pSl == NULL)
    {
        printf("无学生数据!\n");
        return;
    }
    Slnode* pmda = pSl;
    while (pmda)
    {
        printf("名字:%s\n", pmda->Stu->name);
        printf("性别:%s\n", pmda->Stu->sex);
        printf("学号:%d\n", pmda->Stu->num);
        pmda = pmda->next;
    }
    printf("NULL\n");
}


void SlFindpriStu(Slnode* Sl)
{
    if (Sl == NULL)
    {
        printf("当前无数据,无法查找\n");
        return;
    }
    printf("请输入要查找的学生姓名\n");
    char name[NAME_MAX];
    scanf("%s", name);//我们规定使用人名进行查找,也可以用别的,根据情况更换判断条件
    Slnode* Tem = Sl;//定义一个指针变量,初始化其为Sl的地址
    while (Tem)//使用它进行链表的遍历
    {
        if (!strcmp(name, &Sl->Stu->name))//通过strcmp函数进行字符串的比对
        {
            printf("已找到该学生信息\n");
            SlSigPrint(Sl);//找到该学生后,将其数据信息打印出来
            return;
        }
        Tem = Tem->next;
    }
    printf("未找到该学生信息!\n");//若遍历整个链表后还没找到目标,则找不到,则打印未找到
}


struct Slnode* revSl(Slnode** head)//反转链表
{
    struct Slnode* curr;//保存头节点
    struct Slnode* prev = NULL;//保存尾节点

    while (*head)
    {
        curr = (*head)->next;  // 将当前节点的下一个节点保存为 curr
        (*head)->next = prev;  // 将当前节点的指针指向前一个节点,实现反转

        prev = *head;  // 更新前一个节点指针为当前节点
        *head = curr;  // 更新当前节点指针为下一个节点,继续遍历链表
    }

    return prev;  // 返回新的头节点,即原链表的尾节点

}


//内部-----------------------------------------支持//

Slnode* Slbuy()
{
    Slnode* Tem = (Slnode*)malloc(sizeof(Slnode));
    if (Tem == NULL)
    {
        perror("Tem fail!");
    }
    Tem->Stu = SetStuifm();
    Tem->next = NULL;
    return Tem;
}


Datetype* SetStuifm()
{
    Datetype* Stu = (Datetype*)malloc(sizeof(Datetype));
    printf("请输入学生姓名\n");
    scanf("%s", Stu->name);
    printf("请输入学生性别\n");
    scanf("%s", Stu->sex);
    printf("请输入学生学号\n");
    scanf("%d", &Stu->num);
    
    printf("\n");
    printf("录入完毕!\n");
    printf("\n");
    return Stu;
}


int SlFindStu(char name[], Slnode* Sl)
{
    assert(Sl);
    Slnode* Tem = Sl;
    while (Tem)
    {
        if (!strcmp(name, Tem->Stu->name))
        {
            return 1;
        }
        Tem = Tem->next;
    }
    return 0;
}


void SlSigPrint(Slnode* pSl)
{
    assert(pSl);
    Slnode* pmda = pSl;
    printf("名字:%s\n", pmda->Stu->name);
    printf("性别:%s\n", pmda->Stu->sex);
    printf("学号:%d\n", pmda->Stu->num);
    
}

 

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include"Slistinr.h"
#include"Slist.h"
//菜单
void muse();

//选择--------------------------------------------方式//

void SljudgePush(Slnode** pSl);//选择插入方式,头插还是尾插

void SljudgePop(Slnode** pSl);//选择删除方式,头插还是尾插还是任意删

//API---------------------------------------------插口//

void SlheadPush(Slnode** pSl);//插入新学生信息(头插)

void SlbackPush(Slnode** pSl);//插入新学生信息(尾插)

void SlPophead(Slnode** pSl);//删除学生信息(头删)

void SlPopback(Slnode** pSl);//删除学生信息(尾删)

void SlarbPush( Slnode** pSl, char name[]);//任意插//插在想要插入的学生之前

void SlarbPop(Slnode** pSl, char name[]);//任意删//删掉该学生信息

void SlrevStu(Slnode** pSl);//修改学生

void SlAllPrint(Slnode* pSl);//打印链表中所有内容

void SlFindpriStu(Slnode* Sl);//找到并输出学生信息

struct Slnode* revSl(Slnode** head);//反转链表

//内部-----------------------------------------支持//

Slnode* Slbuy();//申请新节点

Datetype* SetStuifm();//创建新学生信息

int SlFindStu(char name[], Slnode* Sl);//寻找学生

void SlSigPrint(Slnode* pSl);//打印该学生信息

void SlheadarbPush(Slnode** pSl, char name[]);//任意头插
 

#pragma once
/*本头文件用于
包含库函数
结构体变量的定义
宏参数的定义*/

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include"Slist.h"
#define NAME_MAX 20 //姓名
#define SEX_MAX 6//性别

typedef struct Students Datetype;//将学生结构体重命名
struct Students//学生结构体
{
    char name[NAME_MAX];//姓名
    char sex[SEX_MAX];//性别
    long long num;//学号
};

typedef struct SlistNode//节点
{
    Datetype* Stu;//数据域

    struct SlistNode* next;//指针域
}Slnode;//将链表节点结构重命名
 

 

若是使用过程中发现代码有问题,欢迎指出,谢谢! 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值