2026/7/23 数据结构2 (单向链表)
一、链表的原理与应用
顺序表的优点是:由于顺序表数据元素的内存地址都是连续的,所以可以实现随机访问,而且不需要多余的信息来描述相关的数据,所以存储密度高。
顺序表的缺点是:顺序表的数据在进行增删的时候,需要移动成片的内存,另外,当数据元素的数量较多的时候,需要申请一块较大的连续的内存,同时当数据元素的数量的改变比较剧烈,顺序表不灵活。
可以利用链式存储的线性表存储数据,链式存储指的是采用离散的内存单元来存储数据元素,用户需要使用某种方式把所有的数据元素连接起来,这样就可以变为链式线性表,简称为链表,链表可以高效地使用碎片化内存。由于链表中的每个数据元素的地址是不固定的,所以每个数据元素都应该使用一个指针指向直接后继的内存地址,当然最后一个数据元素没有直接后继,所以最后一个数据元素指向NULL即可,作为用户只需要知道第一个数据元素的内存地址,就可以访问后继元素了。即线性表中每一个数据元素除了存储自身数据之外,还需要额外存储直接后继的地址,所以链表中的每一个数据元素都是由两部分组成:存储自身数据的部分被称为数据域,存储直接后继地址的部分被称为指针域,数据域和指针域组成的数据元素被称为结点(Node)。
一般把链表分为两种:一种是不带头结点的链表,一种是带头结点的链表,头结点指的是管理结构体,只不过头结点只存储第一个元素的内存地址,头结点并不存储有效数据,头结点的意义只是为了方便管理链表。可以知道,头指针是必须的,因为通过头指针才可以访问链表的元素,头结点是可选的,只是为了方便管理链表而已。
头结点:是不存储有效数据的,只存储第一个数据元素的地址,头指针只指向头结点。
首结点:是存储有效数据的,也存储直接后继的内存地址,首结点就是第一个结点,首结点是唯一一个只指向别的结点,不被别的结点指向的结点。
尾结点:是存储有效数据的,尾结点就是链表的最后一个结点,所以尾结点中存储的地址一般指向NULL,尾结点是唯一一个只被别的结点指向,不能指向别的结点的结点。
根据链表的结点的指针域的数量以及根据链表的首尾是否相连,把链式线性表分为以下几种:单向链表、单向循环链表、双向链表、双向循环链表、内核链表。这几种链表的使用规则差不多,只不过指针域数量不同。
单向链表的内部每一个结点都保存了一个地址,每个地址都是逻辑上相邻的下一个结点的地址,只不过末尾结点的指针指向NULL。
对于单向链表,编写如下c语言代码实现对其增删改查等操作并验证
/********************************************************************************************************
* @File Name :LinkedList.c
* @Author :bbzhu04@gmail.com
* @Date :2026/07/23
* @Function :构造函数实现链表元素的增删改查等操作
* @Notes :为了提高可移植性,所以链表中元素的数据类型为DataType_t,用户可以根据实际情况修改链表中元素的类型
* 另外,为了方便管理链表,所以用户设计LList_t结构体,该结构体中包含两个成员:结点的数据域和指针域
* Copyright (c) 2025-2026 bbzhu04@gmail.com All right Reserved
* ******************************************************************************************************/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
//指的是单向链表中的结点有效数据类型,用户可以根据需要进行修改
typedef int DataType_t;
//构造链表的结点,链表中所有结点的数据类型应该是相同的(与顺序表不同,链表中每个结点都是一个独立的结构体,而顺序表中所有元素都是一个连续的内存空间)
typedef struct LinkedList
{
DataType_t data; //结点的数据域
struct LinkedList *next; //结点的指针域
}LList_t;
/********************************************************************************************************
* @Function Name :LList_Create
* @Functionality :创建链表并对链表进行初始化
* @Function Parameters :None
* @Return result :指向链表管理结构体的指针
* @Notes :None
* @Author :bbzhu04@gmail.com
* @Revision History :None
* @Function version :V1.0
* *****************************************************************************************************/
LList_t * LList_Create(void)
{
//1.创建一个头结点并对头结点申请内存
LList_t *Head = (LList_t *)calloc(1,sizeof(LList_t));
if (NULL == Head)
{
perror("Calloc memory for Head is Failed");
exit(-1);
}
//2.对头结点进行初始化,头结点是不存储有效内容的!!!
Head->next = NULL;
//3.把头结点的地址返回即可
return Head;
}
/********************************************************************************************************
* @Function Name :LList_NewNode
* @Functionality :创建新的结点,并对新结点进行初始化(数据域 + 指针域)
* @Function Parameters :
* @data:要插入到新结点中的数据
* @Return result :指向链表新结点的指针
* @Notes :None
* @Author :bbzhu04@gmail.com
* @Revision History :None
* @Function version :V1.0
* *****************************************************************************************************/
LList_t * LList_NewNode(DataType_t data)
{
//1.创建一个新结点并对新结点申请内存
LList_t *New = (LList_t *)calloc(1,sizeof(LList_t));
if (NULL == New)
{
perror("Calloc memory for NewNode is Failed");
return NULL;
}
//2.对新结点的数据域和指针域进行初始化
New->data = data;
New->next = NULL;
return New;
}
/********************************************************************************************************
* @Function Name :LList_HeadInsert
* @Functionality :在链表的头部插入新的结点
* @Function Parameters :
* @Head:指向链表头结点的指针
* @data:要插入到新结点中的数据
* @Return result :bool型变量,插入成功时返回true,否则返回false
* @Notes :None
* @Author :bbzhu04@gmail.com
* @Revision History :None
* @Function version :V1.0
* *****************************************************************************************************/
bool LList_HeadInsert(LList_t *Head,DataType_t data)
{
//1.创建新的结点,并对新结点进行初始化
LList_t *New = LList_NewNode(data);
if (NULL == New)
{
printf("can not insert new node\n");
return false;
}
//2.判断链表是否为空,如果为空,则直接插入即可
if (NULL == Head->next)
{
Head->next = New;
return true;
}
//3.如果链表为非空,则把新结点插入到链表的头部
New->next = Head->next;
Head->next = New;
return true;
}
/********************************************************************************************************
* @Function Name :LList_TailInsert
* @Functionality :在链表的尾部插入新的结点
* @Function Parameters :
* @Head:指向链表头结点的指针
* @data:要插入到新结点中的数据
* @Return result :bool型变量,插入成功时返回true,否则返回false
* @Notes :None
* @Author :bbzhu04@gmail.com
* @Revision History :None
* @Function version :V1.0
* *****************************************************************************************************/
bool LList_TailInsert(LList_t *Head,DataType_t data)
{
//1.创建新的结点,并对新结点进行初始化
LList_t *New = LList_NewNode(data);
if (NULL == New)
{
printf("can not insert new node\n");
return false;
}
//2.找到链表的尾结点
LList_t *Phead = Head;//对链表的头结点的地址进行备份
while (Phead->next)//指针域不为空,则说明当前结点不是尾结点
{
Phead = Phead->next;
}
//3.将新结点插入到链表的尾部
Phead->next = New;
return true;
}
/********************************************************************************************************
* @Function Name :LList_DestInsert
* @Functionality :在链表中指定值的后面插入新的结点
* @Function Parameters :
* @Head:指向链表头结点的指针
* @destval:目标结点的值
* @data:要插入到新结点中的数据
* @Return result :bool型变量,插入成功时返回true,否则返回false
* @Notes :None
* @Author :bbzhu04@gmail.com
* @Revision History :None
* @Function version :V1.0
* *****************************************************************************************************/
bool LList_DestInsert(LList_t *Head, DataType_t destval, DataType_t data)
{
// 1. 创建新结点
LList_t *New = LList_NewNode(data);
if (NULL == New)
{
printf("can not insert new node\n");
return false;
}
// 2. 找到目标结点(Phead 指向目标结点本身)
LList_t *Phead = Head->next; // 从首结点开始找
while (Phead && Phead->data != destval)
{
Phead = Phead->next;
}
// 3. 如果找到了目标结点,在其后面插入新结点
if (Phead)
{
New->next = Phead->next;
Phead->next = New;
return true;
}
printf("dest node not found\n");
free(New);
return false;
}
/********************************************************************************************************
* @Function Name :LList_HeadDel
* @Functionality :删除链表首结点
* @Function Parameters :
* @Head:指向链表头结点的指针
* @Return result :void
* @Notes :None
* @Author :bbzhu04@gmail.com
* @Revision History :None
* @Function version :V1.0
* *****************************************************************************************************/
void LList_HeadDel(LList_t *Head)
{
//1.判断链表是否为空,如果为空,则直接返回即可
if (NULL == Head->next)
{
printf("list is empty\n");
return;
}
//2.删除链表的首结点
LList_t *Phead = Head->next;//对链表的首结点的地址进行备份
Head->next = Phead->next;
Phead->next = NULL;
free(Phead);
}
/********************************************************************************************************
* @Function Name :LList_DelNode
* @Functionality :删除链表中指定的元素(第一个匹配的元素)
* @Function Parameters :
* @Head:指向链表头结点的指针
* @data:要删除的元素的值
* @Return result :bool型变量,删除成功时返回true,否则返回false
* @Notes :None
* @Author :bbzhu04@gmail.com
* @Revision History :None
* @Function version :V1.0
* *****************************************************************************************************/
bool LList_DelNode(LList_t *Head, DataType_t data)
{
//1.找到要删除的结点的前驱结点
LList_t *Phead = Head;//对链表的头结点的地址进行备份
while (Phead->next && Phead->next->data != data)//指针域不为空,则说明当前结点不是尾结点,并且当前结点的直接后继的值不等于要删除的值
{
Phead = Phead->next;
}
//2.如果找到了要删除的结点,则删除它
if (Phead->next)//指针域不为空,则说明当前结点不是尾结点,并且当前结点的直接后继的值等于要删除的值
{
LList_t *Temp = Phead->next;
Phead->next = Temp->next;
Temp->next = NULL;
free(Temp);
return true;
}
printf("node not found\n");
return false;
}
/********************************************************************************************************
* @Function Name :LList_Print
* @Functionality :遍历输出链表的元素
* @Function Parameters :
* @Head:指向链表头结点的指针
* @Return result :void
* @Notes :None
* @Author :bbzhu04@gmail.com
* @Revision History :None
* @Function version :V1.0
* *****************************************************************************************************/
void LList_Print(LList_t *Head)
{
//对链表的头文件的地址进行备份
LList_t *Phead = Head;
while(Phead->next)//指针域不为空,则说明当前结点不是尾结点
{
//把头的直接后继作为新的头结点
Phead = Phead->next;
//输出头结点的直接后继的数据域
printf("data = %d\n",Phead->data);
}
}
int main(int argc, char const *argv[])
{
LList_t * LList = LList_Create();
//验证向头部插入结点
LList_HeadInsert(LList, 10); //10
LList_HeadInsert(LList, 20); //20 10
LList_Print(LList);
printf("\n");
//验证向尾部插入结点
LList_TailInsert(LList, 30); //20 10 30
LList_Print(LList);
printf("\n");
//验证在指定结点后插入结点
LList_DestInsert(LList, 20, 25); //20 25 10 30
LList_Print(LList);
printf("\n");
//验证删除首结点
LList_HeadDel(LList); //25 10 30
LList_Print(LList);
printf("\n");
//验证删除指定结点
LList_DelNode(LList, 10); //25 30
LList_DelNode(LList, 30); //25
LList_TailInsert(LList, 30);//25 30
LList_DelNode(LList, 25); //30
LList_Print(LList);
printf("\n");
return 0;
}
输出结果:
作者:bbzhu04@gmail.com ,多有不妥之处,望批评指正
本文原创,欢迎转载;转载请在文章显著位置标明原文链接: href="https://www.cnblogs.com/zhubb/p/21804421"
保留所有权限,未经许可禁止篡改、商用发布。

浙公网安备 33010602011771号