1.概述
链表中的头结点与虚拟头结点是两个不同的概念,在链表操作中具有不同的作用和优势。头结点是链表的第一个实际存储数据的结点,它连接着链表中的其他元素。在没有虚拟头结点的链表中,头结点在链表的操作中具有特殊地位,特别是在插入或删除操作时,需要对头结点单独处理。例如,当需要在链表头部插入数据时,需要特别考虑头节点前面没有其他节点的情况,同样地,如果需要删除头结点,则必须重新设定链表的头结点为下一个结点。
虚拟头结点通常不存储任何数据,其主要作用是为了简化链表操作的逻辑。通过将一个虚拟结点作为链表的前端,即便是头结点之前也有了"前驱结点",这样可以使头结点和链表中的其他结点在操作上统一起来。使用虚拟头结点可以规避许多关于头结点的特殊情况讨论,如在处理链表的时候不再需要检查头结点是否为空,也不需要为头结点和其他结点编写不同的代码。综上所述,头结点与虚拟头结点在链表中各司其职,前者作为链表的实际起点承载数据,而后者作为一个辅助工具简化操作逻辑。在具体实现中,应根据实际需求选择是否采用虚拟头结点这一技巧来优化链表的相关操作。
![](https://i-blog.csdnimg.cn/direct/224449b2eb4f44c080dd3e8f6ca33973.png)
![](https://i-blog.csdnimg.cn/direct/dcdc2436c65c40b29ca60a512612fc82.png)
下述单向链表操作都基于具有虚拟头结点的单向链表
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.结语
感谢观看!如果这篇文章对你有益,那就点个赞吧!