C语言——单向链表

1.概述

线性表的链式存储结构,我们叫做链表。将线性表L=(a0,a1,……,an-1)中各元素分布在存储器中的不同存储区域,线性表中的各元素称为结点,通过地址或指针建立结点之间的联系,所得到的存储结构为链表 结构。
数据域和指针域
数据域 : 结点的 data 域存放数据元素 a0
指针域 : next 域是一个指针,指向 a0 的直接后继结点。
头结点和尾结点、和虚拟头结点
头结点 : 链表中第一个结点我们叫做头结点
尾结点 : 链表中最后一个结点,我们叫做尾结点 , 尾结点的指针域为 NULL
虚拟头结点:为了方便对链表进行操作,我们一般会在链表的头结点之前,再创建一个结点
dummyHead, 让该结点的 next 域指向 head dummyHead结点就称之为虚拟头结点,虚拟头结点数据域 默认不使用,只是用指针域。

链表中的头结点与虚拟头结点是两个不同的概念,在链表操作中具有不同的作用和优势。头结点是链表的第一个实际存储数据的结点,它连接着链表中的其他元素。在没有虚拟头结点的链表中,头结点在链表的操作中具有特殊地位,特别是在插入或删除操作时,需要对头结点单独处理。例如,当需要在链表头部插入数据时,需要特别考虑头节点前面没有其他节点的情况,同样地,如果需要删除头结点,则必须重新设定链表的头结点为下一个结点。

虚拟头结点通常不存储任何数据,其主要作用是为了简化链表操作的逻辑。通过将一个虚拟结点作为链表的前端,即便是头结点之前也有了"前驱结点",这样可以使头结点和链表中的其他结点在操作上统一起来。使用虚拟头结点可以规避许多关于头结点的特殊情况讨论,如在处理链表的时候不再需要检查头结点是否为空,也不需要为头结点和其他结点编写不同的代码。综上所述,头结点与虚拟头结点在链表中各司其职,前者作为链表的实际起点承载数据,而后者作为一个辅助工具简化操作逻辑。在具体实现中,应根据实际需求选择是否采用虚拟头结点这一技巧来优化链表的相关操作。 

下述单向链表操作都基于具有虚拟头结点的单向链表

2.创建单向链表

a.创建虚拟头结点.空链表

//创建虚拟头结点
typedef struct student
{
        char id[64];
        char name[64];
        char age[16];
}Stu_t;

typedef struct Node
{
        Stu_t data;
        struct Node *next;
}linkNode_t;

linkNode_t *dummyhead=NULL;
creat_dummyheadNode(&dummyhead);
linkNode_t *creat_dummyheadNode(linkNode_t **p_head)
{
        linkNode_t *list=(linkNode_t *)malloc(sizeof(linkNode_t));
        if(NULL==list)
        {
                printf("头节点开辟失败\n");
                return NULL;
        }
        //初始化
        memset(list,0,sizeof(linkNode_t));
        list->next=NULL;
        *p_head=list;
}

3.单向链表的基本操作

a.创建单向链表其他结点

//创建结点
linkNode_t *creat_node_linklist()
{
        linkNode_t *node=(linkNode_t *)malloc(sizeof(linkNode_t));
        if(NULL==node)
        {
                printf("开辟结点失败\n");
                return NULL;
        }
        return node;
}

b.将创建好的结点插入链表

(1).在单向链表头部插入

//在头部插入
void link_enterlist(linkNode_t *dummyhead)
{
        linkNode_t *Nodelist=link_writelist();
        Nodelist->next=list->next;
        list->next=Nodelist;

}
linkNode_t *link_writelist()
{
        linkNode_t *Nodelist=creat_node_linklist();
        printf("请输入节点信息:\n");
        while(getchar()!='\n');
        char id[64];
        printf("please enter id\n");
        fgets(id,sizeof(id)-1,stdin);
        id[strlen(id)-1]='\0';
        strcpy(Nodelist->data.id,id);
        printf("piease enter name\n");
        char name[64];
        fgets(name,sizeof(name)-1,stdin);
        name[strlen(name)-1]='\0';
        strcpy(Nodelist->data.name,name);
        printf("please enter age\n");
        char age[16];
        fgets(age,sizeof(age)-1,stdin);
        age[strlen(age)-1]='\0';
        strcpy(Nodelist->data.age,age);
        return Nodelist;
}
                

(2).在单向链表尾部插入

//在尾部插入
void link_endlist(linkNode_t *dummyhead)
{
        linkNode_t *Nodelist=link_writelist();
        //查找尾结点
        while(list->next!=NULL)
        {
                list=list->next;
        }
        Nodelist->next=list->next;
        list->next=Nodelist;
}
                                                                                                        
linkNode_t *link_writelist()
{
        linkNode_t *Nodelist=creat_node_linklist();
        printf("请输入节点信息:\n");
        while(getchar()!='\n');
        char id[64];
        printf("please enter id\n");
        fgets(id,sizeof(id)-1,stdin);
        id[strlen(id)-1]='\0';
        strcpy(Nodelist->data.id,id);
        printf("piease enter name\n");
        char name[64];
        fgets(name,sizeof(name)-1,stdin);
        name[strlen(name)-1]='\0';
        strcpy(Nodelist->data.name,name);
        printf("please enter age\n");
        char age[16];
        fgets(age,sizeof(age)-1,stdin);
        age[strlen(age)-1]='\0';
        strcpy(Nodelist->data.age,age);
        return Nodelist;
}

(3).在单向链表中按顺序插入

//顺序插入
void link_orderlist(linkNode_t *list)
{
        linkNode_t *Nodelist=link_writelist();
        while((list->next!=NULL)&&(strcmp(list->next->data.age,Nodelist->data.age)<0))
        {
                list=list->next;
        }  
}
     linkNode_t *link_writelist()
{
        linkNode_t *Nodelist=creat_node_linklist();
        printf("请输入节点信息:\n");
        while(getchar()!='\n');
        char id[64];
        printf("please enter id\n");
        fgets(id,sizeof(id)-1,stdin);
        id[strlen(id)-1]='\0';
        strcpy(Nodelist->data.id,id);
        printf("piease enter name\n");
        char name[64];
        fgets(name,sizeof(name)-1,stdin);
        name[strlen(name)-1]='\0';
        strcpy(Nodelist->data.name,name);
        printf("please enter age\n");
        char age[16];
        fgets(age,sizeof(age)-1,stdin);
        age[strlen(age)-1]='\0';
        strcpy(Nodelist->data.age,age);
        return Nodelist;
}
    

c.查找与删除结点

(1).查找结点

//按数据域信息查找节点
bool link_findNode(linkNode_t *dummyhead)
{
        //先判断单向链表是否为空
        if(dummyhead=NULL)
        {
                return false;
        }
        linkNode_t *tmp=dummyhead;
        char findlist[64];
        printf("please enter findlist_id:\n");
        while(getchar()!='\n');
        scanf("%s",findlist);
        while(tmp->next!=NULL)
        {
                if(strcmp(tmp->next->data.id,findlist)==0)
                {
                        printf("ID:%s--NAME:%s--AGE:%s\n",tmp->next->data.id,tmp->next->data.name,tmp->next->data.age);
                        return true;
                }
                tmp=tmp->next;
        }
        return false;

}

(2).删除结点

//按数据域信息删除节点
bool link_deleteNode(linkNode_t *dummyhead)
{
        if(dummyhead==NULL)
        {
                return false;
        }
        linkNode_t *tmp=dummyhead;
        char deleteID[64];
        while(getchar()!='\n');
        scanf("%s",deleteID);
        while(tmp->next!=NULL)
        {
                if(strcmp(tmp->next->data.id,deleteID)==0)
                {
                        //保存将要删除的节点
                        linkNode_t *deleteNode=tmp->next;
                        tmp->next=tmp->next->next;
                        free(deleteNode);
                        deleteNode=NULL;
                        return true;
                }
                tmp=tmp->next;
        }
        return false;
}

d.清空链表,释放空间

链表的清空是指将链表中的所有节点删除,但保留链表的结构,以便之后继续使用。

链表清空的原理
   保留头结点:在清空过程中,头结点是被保留的,这意味着链表的整体结构仍然存在,只是其中的数据节点被清除。
   释放数据节点:从头结点的下一个节点开始,依次释放每个数据节点所占用的内存,确保没有内存泄露。
   重置指针域:最后将头结点的指针域设置为NULL,表示链表为一个空表,不再含有任何数据节点。
内存泄露是指程序中已动态分配的堆内存由于某种原因未能释放或无法释放,导致系统内存逐渐减少的现象。

内存泄漏是指程序在运行过程中动态分配的内存,由于某种原因未被释放或无法释放。这种现象常见于使用如malloc、calloc、realloc和new等内存分配函数后,未使用对应的free或delete来释放内存的情况。内存泄漏具有隐蔽性和积累性特征,比其他内存非法访问错误更难检测。 

//清空单向链表
void link_clearlist(linkNode_t *dummyhead)
{
        if(dummyhead==NULL)
        {
                return;
        }
        linkNode_t *clearNode=dummyhead;
        while(clearNode->next!=NULL)
        {
                linkNode_t *p_clear=clearNode->next;
                clearNode->next=clearNode->next->next;
                free(p_clear);
                p_clear=NULL;
        }
}

4.结语

感谢观看!如果这篇文章对你有益,那就点个赞吧!

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值